C语言socket服务端和客户端代码 /缓冲区/阻塞

2/22/2017来源:ASP.NET技巧人气:1646

服务端:

#include <stdio.h>#include <winsock2.h>#PRagma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll#define BUF_SIZE 100int 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 100int 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

阻塞模式--------------

对于TCP套接字(默认情况下),当使用 write()/send() 发送数据时: 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阻塞模式方法也很简单:

linux

          fcntl(socket, F_SETFL, flags | O_NONBLOCK);

          read/recv函数的最后一个参数也可以设置阻塞或非阻塞方式

windows:

          ioctlsocket,WSAAsyncselect()和WSAEventselect()

    read/recv函数的最后一个参数也可以设置阻塞或非阻塞方式