服务端:
- #include <stdio.h>
- #include <winsock2.h>
- #pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
- #define BUF_SIZE 100
- int main(){
- WSADATA wsaData;
- WSAStartup( MAKEWORD(2, 2), &wsaData);
- //创建套接字
- SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);
- //绑定套接字
- sockaddr_in sockAddr;
- memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
- sockAddr.sin_family = PF_INET; //使用IPv4地址
- sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
- sockAddr.sin_port = htons(1234); //端口
- bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
- //进入监听状态
- listen(servSock, 20);
- //接收客户端请求
- SOCKADDR clntAddr;
- int nSize = sizeof(SOCKADDR);
- SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
- char buffer[BUF_SIZE]; //缓冲区
- int strLen = recv(clntSock, buffer, BUF_SIZE, 0); //接收客户端发来的数据
- send(clntSock, buffer, strLen, 0); //将数据原样返回
- //关闭套接字
- closesocket(clntSock);
- closesocket(servSock);
- //终止 DLL 的使用
- WSACleanup();
- return 0;
- }
- #include <stdio.h>
- #include <stdlib.h>
- #include <WinSock2.h>
- #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
- #define BUF_SIZE 100
- int main(){
- //初始化DLL
- WSADATA wsaData;
- WSAStartup(MAKEWORD(2, 2), &wsaData);
- //创建套接字
- SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- //向服务器发起请求
- sockaddr_in sockAddr;
- memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
- sockAddr.sin_family = PF_INET;
- sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
- sockAddr.sin_port = htons(1234);
- connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
- //获取用户输入的字符串并发送给服务器
- char bufSend[BUF_SIZE] = {0};
- printf("Input a string: ");
- scanf("%s", bufSend);
- send(sock, bufSend, strlen(bufSend), 0);
- //接收服务器传回的数据
- char bufRecv[BUF_SIZE] = {0};
- recv(sock, bufRecv, BUF_SIZE, 0);
- //输出接收到的数据
- printf("Message form server: %s\n", bufRecv);
- //关闭套接字
- closesocket(sock);
- //终止使用 DLL
- WSACleanup();
- system("pause");
- return 0;
- }
- #include <stdio.h>
- #include <stdlib.h>
- #include <WinSock2.h>
- #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
- #define BUF_SIZE 100
- int main(){
- //初始化DLL
- WSADATA wsaData;
- WSAStartup(MAKEWORD(2, 2), &wsaData);
- //创建套接字
- SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- //向服务器发起请求
- sockaddr_in sockAddr;
- memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
- sockAddr.sin_family = PF_INET;
- sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
- sockAddr.sin_port = htons(1234);
- connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
- //获取用户输入的字符串并发送给服务器
- char bufSend[BUF_SIZE] = {0};
- printf("Input a string: ");
- scanf("%s", bufSend);
- send(sock, bufSend, strlen(bufSend), 0);
- //接收服务器传回的数据
- char bufRecv[BUF_SIZE] = {0};
- recv(sock, bufRecv, BUF_SIZE, 0);
- //输出接收到的数据
- printf("Message form server: %s\n", bufRecv);
- //关闭套接字
- closesocket(sock);
- //终止使用 DLL
- WSACleanup();
- system("pause");
- return 0;
- }
socket缓冲区
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
图:TCP套接字的I/O缓冲区示意图
这些I/O缓冲区特性可整理如下:
- I/O缓冲区在每个TCP套接字中单独存在;
- I/O缓冲区在创建套接字时自动生成;
- 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
- 关闭套接字将丢失输入缓冲区中的数据。
输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:
- unsigned optVal;
- int optLen = sizeof(int);
- getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
- printf("Buffer length: %d\n", optVal);
Buffer length: 8192
阻塞模式--------------
1) 首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据。
2) 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。
3) 如果要写入的数据大于缓冲区的最大长度,那么将分批写入。
4) 直到所有数据被写入缓冲区 write()/send() 才能返回。
当使用 read()/recv() 读取数据时:
1) 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。
2) 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read()/recv() 函数再次读取。
3) 直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。
这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。
//==============
socket的阻塞函数---------
accept,connect,recv(recvfrom),send(sendto),closesocket,select(poll或epoll)
accept在阻塞模式下,没有新连接时,线程会进入睡眠状态;非阻塞模式下,没有新连接时,立即返回WOULDBLOCK错误。
connect在阻塞模式下,仅TCP连接建立成功或出错时才返回,分几种具体的情况,这里不再叙述;非阻塞模式下,该函数会立即
返回INPROCESS错误(可以用select检测该连接是否建立成功)
select/poll/epoll并不是真正意义上的阻塞,它们的阻塞是由于它们最后一个timeout参数决定的,timeout大于0时,它们会一
直等待直到超时才退出(相等于阻塞),而timeout=-1即永远等待。
struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
设置socket阻塞模式方法也很简单:
fcntl(socket, F_SETFL, flags | O_NONBLOCK);
read/recv函数的最后一个参数也可以设置阻塞或非阻塞方式
windows:
ioctlsocket,WSAAsyncselect()和WSAEventselect()
read/recv函数的最后一个参数也可以设置阻塞或非阻塞方式