Socket也叫套接字,用来实现网络通讯,通过调用系统提供的API,可以和远程的机子传输数据。Socket有很多种协议,而这篇文章主要讨论TCP部分的内容,也就是说后面说的内容主要是指TCP Socket。
Socket 的一般调用过程:
服务端:socket(), bind(),listen(),accept(),send(),recv(),close()
客户端:socket(),connect(),send(),recv(),close()
阻塞socket(同步socket)
进程或线程执行到某些socket函数时必须等待该socket事件的发生,如果该事件没有发生,socket函数不能立即返回,进程或线程被阻塞。
特点:使用简单,适合一对一的应答场合,在服务端很少使用,或配合多线程使用
函数 | 返回值说明 | 阻塞情况 |
accept() | 返回新的连接socket句柄。 | 缓冲区队列没有新的等待连接 |
connect() | 返回-1说明连接失败,其他正常。 | 连接过程阻塞。 |
recv() | 返回值小于1代表接收失败,其他代表接收数据的长度。 | 发送缓冲区有数据等待发送完成,或接收缓冲区没数据时阻塞。 |
send() | 返回-1代表发送失败,其他为发送数据的长度 | 发送缓冲区没有足够空间保存此次发送数据时阻塞 |
非阻塞socket(异步socket)
进程或线程执行socket函数时不必非要等待该socket事件的发生,一旦执行立即返回。根据返回值的不同可以判断函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程可以不被阻塞,继续执行。
特点:函数执行立即返回,不会阻断进程,性能比阻塞高,适合在主线程直接调用,不会造成主线程卡顿现象
因为socket默认是阻塞的,所以要设置非阻塞模式:
#ifdef WIN32 DWORD nMode = 1; ioctlsocket(m_sock, FIONBIO, &nMode); #else int r = fcntl(fd, F_GETFL, 0)); fcntl(fd, F_SETFL, r|O_NONBLOCK); #endif
TCP与UDP 的区别
协议说明 | socket创建 | |
TCP | 传输控制协议,可靠的连接服务。双方先建立连接再传输数据。提供超时重发,数据检验,流量控制等机制,保证数据发送无误。 | socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) |
UDP | 用户数据报协议,不可靠的连接服务。没有建立连接就可以发送数据,没有超时重发机制,传输速度很快。 | socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) |
Socket例子
下面以一个简单例子来说明服务端与客户端的交互过程
服务端 server.cpp
#include <stdio.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") #define PORT 8080 int main() { //初始化winsock服务 WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData); //创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in svraddr; svraddr.sin_family = AF_INET; svraddr.sin_port = htons(PORT); svraddr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定socket bind(sock, (struct sockaddr*)&svraddr, sizeof(svraddr)); //监听socket listen(sock, 5); while(1) { struct sockaddr_in addr; int len = sizeof(SOCKADDR); char buf[1024] = {0}; //接受客户端连接 SOCKET client = accept(sock, (struct sockaddr*)&addr, &len); char* ip = inet_ntoa(addr.sin_addr); printf("accept client: %s ", ip); //接收客户端数据 if(recv(client, buf, 1024, 0) >0) { printf("recv client: %s ", buf); //向客户端发送数据 send(client, "hello, client", strlen("hello, client"), 0); } closesocket(client); } //关闭socket closesocket(sock); //关闭winsock服务 WSACleanup(); return 0; }
客户端 client.cpp
#include <stdio.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") #define REMOTE_IP "127.0.0.1" #define REMOTE_PORT 8080 int main() { //初始化winsock服务 WSADATA wsaData; WSAStartup(MAKEWORD(2,2), &wsaData); //创建socket SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in svraddr; svraddr.sin_family = AF_INET; svraddr.sin_port = htons(REMOTE_PORT); svraddr.sin_addr.s_addr = inet_addr(REMOTE_IP); //连接socket if( connect(sock, (struct sockaddr*)&svraddr, sizeof(svraddr)) != -1) { //发送数据给服务端 if(send(sock, "hello, server", strlen("hello, server"), 0) != -1) { //接收服务端数据 char buf[1024] = {0}; if(recv(sock, buf, 1024, 0) >0) { printf("recv server: %s ", buf); } } } else { printf("can not connect server "); } //关闭socket closesocket(sock); //关闭winsock服务 WSACleanup(); getchar(); return 0; }