阻塞模式下的send、recv、close总结

阻塞模式下的send、recv、close总结:

 

对于send函数:

    send函数只负责将数据提交给协议层。
   当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
    如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据;
    如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len;
    如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
    如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;
    如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。
    如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket 函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)  

对于recv函数:

    recv 先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果 recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0
    对方优雅的关闭socket并不影响本地recv的正常接收数据;如果协议缓冲区内没有数据,recv返回0,指示对方关闭;如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭;


对于"关闭连接"
socket连接的关闭分为:"优雅关闭"和"强制关闭";
MSDN上有说明:closesocket的关闭动作依赖于socket选项SO_LINGER和 SO_DONTLINGER;(SO_DONTLINGER为缺省值),其含义如下:
选项          阻塞时间  关闭方式  等待关闭与否
SO_DONTLINGER 不关心    优雅      否
SO_LINGER     零        强制      否
SO_LINGER     非零      优雅      是
MSDN上,还有说明:为了确保数据能被对方接收,应用程序应当在调用closesocket之前调用shutdown。
closesocket应最后被调用,以便系统释放socket句柄及相关资源。

 

粘包、丢包 及TCP信息收发

初涉socket编程的朋友经常有下面一些疑惑:
1. 为什么我发了3次,另一端只收到2次?
2. 我每次发送都成功了,为什么对方收到的信息不完整?

这些疑惑往往是对send和recv这两个函数理解不准确所致。send和recv都 提供了一个长度参数。对于send而言,这是你希望发送的字节数,而对于recv而言,则是希望收到的最大字节数。

1。 send

send 函数的原型是:int send(SOCKET sd, const char * buffer, int len, int flag). 其中len指出buffer中包含的实际字节数,也是程序员希望发出的最大字节数。而这个函数的返回值是实际发送出去的字节数。在网络程序中,正常情况是 这个返回值小于len,也就是说buffer中的内容没有完全被发送出去。

为了确保一个缓冲区内的内容被完全被发送出去,我们需要如下代 码:

int res;
int pos = 0; //下一次发送需要开始的位置:
while(pos < len)
{
res = send(sd, buffer + pos , len - pos, 0);
if(res <=0) goto err_handler; //去错误处理
pos += res;
}

这 样经过多次send,可以确保buffer内的内容都别发送出去。

为了避免发送线程被阻塞,应该考虑把上述代码放到一个子线程中,并通过 队列来缓冲所有收发。

2. recv

recv的原型是:int recv(SOCKET sd, char * buffer, int len, int flag),其各个参数的含义同前面send。需要注意的是,系统并不会等待bufer被填满了再返回,而是一旦有数据被收到,就立刻返回。因此不要期望 实际收到的数据长度就等于len。

你可以使用前面send的循环算法确保收到len个字节,也可以使用内容驱动的方法实现分段数据分析。 不过这个就超出本文内容,也就不再赘述。

3. 粘包
所谓粘包,是指发送端发送的两个报文,在接收端被拼在一起。由于TCP是面向 流的协议,报文与报文之间是没有分界符号的。在接收端,所有的数据都逻辑上拼在一起给你。举例来说,你分10次发送10个长度为10的报文,在接收端,你 可能只收到一个长度为100的报文,而不会收到10个消息。

为了解决这个问题,你必须在接收端有能力把这些报文分隔开来。如果消息长度总 是固定的,这就比较容易,只要按长度取出即可。如果长度不固定,一般有两种方法解决:

a)使用特征字节。例如:如果是聊天程序,发送的是 普通文本,一些字符是绝对不可能出现在正文中的,你可以使用这些字符做分隔符隔离不同消息。我们可以使用'/0'做分隔,一般对于全文本传输是比较安全 的。

b) 在发送方发送正文前,先发送一个长度。例如你要发送2345字节的内容,你可以先发送一个2字节的长度给对方,然后再发正文。接收放只要收到这个长度信 息,就可以正确的分包。需要注意的,到底用多少字节来发送长度是应该预先约定的,一般2字节就足够,不过你约定4字节也是可以的。还要注意的是,如果接收 一次报文后,解包完毕还剩下一部分内容,这些内容应该留给下次报文分包使用,而不能扔掉。

4. 丢包
丢包一般都是由于对上面说的 理解不足引起的,因为TCP本身是确保不丢包的。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个使用 epoll 实现的简单的 TCP 服务器和客户端的例子。服务器和客户端都是非阻塞的,使用 epoll 监听和处理文件描述符事件。其中,服务器使用了多线程技术,每个线程负责处理一个客户端连接。 服务器代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <netinet/in.h> #include <sys/epoll.h> #include <pthread.h> #define MAX_EVENTS 1024 #define BUF_SIZE 1024 #define THREAD_NUM 4 int listen_fd; int epoll_fd; pthread_t thread_pool[THREAD_NUM]; void setnonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } void add_event(int fd, int events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); } void del_event(int fd, int events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev); } void mod_event(int fd, int events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev); } void *worker(void *arg) { int client_fd = *(int*)arg; char buf[BUF_SIZE]; int nread; while (1) { nread = recv(client_fd, buf, BUF_SIZE, 0); if (nread == -1) { if (errno == EAGAIN) { printf("recv EAGAIN\n"); break; } else { perror("recv"); break; } } else if (nread == 0) { printf("client close\n"); break; } else { printf("recv: %s\n", buf); if (send(client_fd, buf, nread, 0) == -1) { perror("send"); break; } } } close(client_fd); pthread_exit(NULL); } void *accepter(void *arg) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd; int i; while (1) { client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd == -1) { if (errno == EAGAIN) { printf("accept EAGAIN\n"); continue; } else { perror("accept"); break; } } else { setnonblocking(client_fd); add_event(client_fd, EPOLLIN|EPOLLET); printf("new client: %d\n", client_fd); } } close(listen_fd); pthread_exit(NULL); } int main(int argc, char *argv[]) { struct sockaddr_in server_addr; int port = 8888; struct epoll_event events[MAX_EVENTS]; int nready; int i, j; if (argc > 1) { port = atoi(argv[1]); } listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd == -1) { perror("socket"); exit(1); } setnonblocking(listen_fd); memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(port); if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(1); } if (listen(listen_fd, 1024) == -1) { perror("listen"); exit(1); } epoll_fd = epoll_create(MAX_EVENTS); if (epoll_fd == -1) { perror("epoll_create"); exit(1); } add_event(listen_fd, EPOLLIN|EPOLLET); for (i = 0; i < THREAD_NUM; i++) { if (pthread_create(&thread_pool[i], NULL, accepter, NULL) == -1) { perror("pthread_create"); exit(1); } } while (1) { nready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nready == -1) { if (errno == EINTR) { continue; } else { perror("epoll_wait"); break; } } for (i = 0; i < nready; i++) { if (events[i].data.fd == listen_fd) { continue; } if (events[i].events & EPOLLIN) { pthread_t thread_id; int client_fd = events[i].data.fd; del_event(client_fd, EPOLLIN|EPOLLET); if (pthread_create(&thread_id, NULL, worker, (void*)&client_fd) == -1) { perror("pthread_create"); } } } } for (i = 0; i < THREAD_NUM; i++) { if (pthread_join(thread_pool[i], NULL) == -1) { perror("pthread_join"); } } close(epoll_fd); return 0; } ``` 客户端代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAX_EVENTS 1024 #define BUF_SIZE 1024 void setnonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } void add_event(int fd, int events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); } void del_event(int fd, int events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev); } void mod_event(int fd, int events) { struct epoll_event ev; ev.events = events; ev.data.fd = fd; epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev); } int main(int argc, char *argv[]) { struct sockaddr_in server_addr; char *ip = "127.0.0.1"; int port = 8888; int sockfd; struct epoll_event events[MAX_EVENTS]; int nready; char buf[BUF_SIZE]; int nread, nwrite; int i; if (argc > 1) { ip = argv[1]; } if (argc > 2) { port = atoi(argv[2]); } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } setnonblocking(sockfd); memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(ip); server_addr.sin_port = htons(port); if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { if (errno != EINPROGRESS) { perror("connect"); exit(1); } } epoll_fd = epoll_create(MAX_EVENTS); if (epoll_fd == -1) { perror("epoll_create"); exit(1); } add_event(sockfd, EPOLLOUT); while (1) { nready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nready == -1) { if (errno == EINTR) { continue; } else { perror("epoll_wait"); break; } } for (i = 0; i < nready; i++) { if (events[i].events & EPOLLOUT) { del_event(sockfd, EPOLLOUT); printf("connected\n"); break; } } } while (1) { scanf("%s", buf); if (strcmp(buf, "quit") == 0) { break; } nwrite = send(sockfd, buf, strlen(buf), 0); if (nwrite == -1) { perror("send"); break; } nread = recv(sockfd, buf, BUF_SIZE, 0); if (nread == -1) { if (errno == EAGAIN) { printf("recv EAGAIN\n"); continue; } else { perror("recv"); break; } } else if (nread == 0) { printf("server close\n"); break; } else { printf("recv: %s\n", buf); } } close(sockfd); close(epoll_fd); return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值