概述
Socket编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前两者较常用,这里简单总结前两种编程步骤以及一些参考资料。
编程步骤
TCP
TCP是面向连接的可靠的协议,需要考虑建立连接。另外TCP还有流式传输的概念,因此TCP对应的就是流式套接字SOCK_STREAM,流式传输是什么意思呢?举个例子,假设我发送端第一次send()发送了"abcd",第二次send()发送了"efg",接收端是接收到的情况是不确定的:接收端有可能第一次recv()收到了"abcde",第二次recv()收到了"fg";也有可能第一次recv()收到了"ab",第二次recv()收到了"cdefg",也有可能…这是由于TCP在传输的过程中没有明确的包的概念,发送出去就像水流一样,接收起来可能一会收到的多,一会收到的少,所以在TCP接收端我们还需要处理“粘包”的问题,接收端需要有分包的功能。
本文只是个简单的示例,并没有实现分包。
服务器端:
- WSAStartup() 初始化环境
WORD w_req = MAKEWORD(2, 2);
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字库失败!" << endl;
}
else {
cout << "初始化套接字库成功!" << endl;
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
- socket() 创建 listenSock,确定Socket类型。
SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0);
- bind() 对 listenSock 绑定ip地址和port端口
SOCKADDR_IN server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(8888);
if (bind(listenSock, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "套接字绑定失败!" << endl;
WSACleanup();
}
else {
cout << "套接字绑定成功!" << endl;
}
- listen() 用 listenSock 监听端口
if (listen(listenSock, SOMAXCONN) < 0) {
cout << "设置监听状态失败!" << endl;
WSACleanup();
}
else {
cout << "设置监听状态成功!" << endl;
}
以下参考链接Listen第二个参数的意义:
在linux 2.2以后 listen的第二个参数,指的是在完成TCP三次握手后的队列,即在系统accept之前的队列,已经完成的队列。如果系统没有调用accpet把这个队列的数据拿出来。一旦这个队列满了。未连接队列的请求过不来。导致未连接队列里的请求会超时或者拒绝。如果系统调用了accpet队列接受请求数据。那么就会把接受到请求移除已完成队列。 这时候已完成队列又可以使用了。
因此,listen()的第二个参数不是表示可以连接的客户端数,参数为5,也可以连接5个以上的客户端。
- accept() 接收来自 listenSock 监听到的连接,并保存返回值为 acceptSock
SOCKET acceptSock = accept(listenSock, (SOCKADDR *)&accept_addr, &len);
if (acceptSock == SOCKET_ERROR) {
cout << "连接失败!" << endl;
WSACleanup();
return 0;
}
备注:此方法为阻塞方法,非阻塞如何使用记录在另外的文章
ToDo:写了非阻塞的文章后链接放过来!!!!
- 连接后调用 recv()/send() 用 acceptSock 完成收发,注意用的一定要是acceptSock !!
while (1)
{
recv_len = recv(acceptSock, recv_buf, 100, 0);
if (recv_len < 0)
{
cout << "接受失败!" << endl;
break;
}
else
{
cout << "客户端信息:" << recv_buf << endl;
}
cout << "请输入回复信息:";
cin >> send_buf;
send_len = send(acceptSock, send_buf, 100, 0);
if (send_len < 0)
{
cout << "发送失败!" << endl;
break;
}
}
- closesocket() 断开连接
closesocket(acceptSock);
closesocket(listenSock);
WSACleanup();
客户端:
客户端代码相对服务器端简单一些,写起来也差不多
- WSAStartup() 初始化环境
- socket() 创建 clientSock
SOCKET clientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- connect() clientSock 连接 serverAddr 这个地址的服务器
SOCKADDR_IN serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(PORT);
if (connect(clientSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
cout << "服务器连接失败!" << endl;
WSACleanup();
}
else {
cout << "服务器连接成功!" << endl;
}
更具体的细节可以参考:
网络编程Socket之TCP之connect详解
socket connect函数理解
- 连接后调用 recv()/send() 用 clientSock 完成收发
while (1) {
cout << "请输入发送信息:";
cin >> send_buf;
send_len = send(clientSock, send_buf, 100, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
recv_len = recv(clientSock, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失败!" << endl;
break;
}
else {
cout << "服务端信息:" << recv_buf << endl;
}
}
可以看到,由于socket已经建立连接,所以只要通过这个socket发送或者接收就可以了。
5.closesocket() 关闭套接字,并调用 WSACleanup() 释放DLL资源
完整代码请参考一下两个链接:
C++:实现socket通信(TCP/IP)实例
C++SOCKET多线程网络编程实现多个客户端与服务器通信计算简单数学算式
个人认为上面两个链接的代码部分命名不太准确,容易让人误解。建议根据上面片段的代码自己复现一下,也会碰到配置环境的一些问题,还是自己做一遍印象深刻。
UDP
UDP是面向无连接的协议,所以相比TCP,UDP是不需要连接的。(但是SOCK_DGRAM也是可以用connect()的)另外,UDP传输中有明确的包分界,也就是发是一个包一个包发,收也是一个包一个包收,所以无需处理粘包问题。
步骤和TCP基本一致,只是UDP只要绑定好socket之后就可以调用recvfrom()和sendto()用这个socket进行收发了。另外,也要注意一下sendto()调用的时候要标明发送地址,端口号,而recvfrom()则要多一个地址参数来存储当前收到的这个数据包是来源哪个地址哪个端口的。
具体步骤先不写了,有空了再补上代码块示例吧。
参考完整代码:UDP sendto和recvfrom使用详解
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)