• 09Socket编程


    tcp是基于字节流的,udp是基于报文即数据包的,所以tcp会产生一个叫做粘包的问题,而udp不会产生。

    我们这节主要讨论粘包问题:

    先看一下粘包问题的原因:

    总结如下:

    1、应用进程的缓冲区和Socket缓冲区的大小不一定相吻合。

    2、tcp传输段有mss限制。

    3、链路层有个mtu限制。

    粘包的解决方案:

    1、设置定长包,定长接受。

    2、在包尾加上 ,例如ftp就是这样实现的。

    3、包头加上包体长度。

    4、更复杂的应用层协议。

    下面就封装一下readn和writen来实现解决粘包问题,改进一下先前的那个回射程序:

    服务器端:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    struct packet  //新定义包的结构,包体长度和包体
    {
    	int len;
    	char buf[1024];
    };
    
    ssize_t readn(int fd, void *buf, size_t count)  //readn的封装
    {
    	size_t nleft = count;
    	ssize_t nread;
    	char *bufp = (char*)buf;
    
    	while (nleft > 0)
    	{
    		if ((nread = read(fd, bufp, nleft)) < 0)//这里小于零两种情况一种是信号打断一种就是失败
    		{
    			if (errno == EINTR)   //可能是信号打断
    				continue;
    			return -1;            //失败
    		}
    		else if (nread == 0)
    			return count - nleft;
    
    		bufp += nread;
    		nleft -= nread;
    	}
    
    	return count;
    }
    
    ssize_t writen(int fd, const void *buf, size_t count)//writen的封装
    {
    	size_t nleft = count;
    	ssize_t nwritten;
    	char *bufp = (char*)buf;
    
    	while (nleft > 0)
    	{
    		if ((nwritten = write(fd, bufp, nleft)) < 0)
    		{
    			if (errno == EINTR)
    				continue;
    			return -1;
    		}
    		else if (nwritten == 0)
    			continue;
    
    		bufp += nwritten;
    		nleft -= nwritten;
    	}
    
    	return count;
    }
    
    void do_service(int conn)
    {
    	struct packet recvbuf;
    	int n;
            while (1)
            {
                    memset(&recvbuf, 0, sizeof(recvbuf));
                    int ret = readn(conn, &recvbuf.len, 4); //先接受包头长度
    		if (ret == -1)
    			ERR_EXIT("read");
    		else if (ret < 4)
    		{
    			printf("client close
    ");
    			break;
    		}
    		
    		n = ntohl(recvbuf.len);    //这里有个字节序的转换,因为是要在网络上传输
    		ret = readn(conn, recvbuf.buf, n);   //再接受包体
    		if (ret == -1)
    			ERR_EXIT("read");
    		else if (ret < n)
    		{
    			printf("client close
    ");
    			break;
    		}
    		
                    fputs(recvbuf.buf, stdout);
                    writen(conn, &recvbuf, 4+n);   //回写要包体长度加包体
            }
    }
    
    int main(void)
    {
    	int listenfd;
    	if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    /*	if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
    		ERR_EXIT("socket");
    
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(5188);
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
    	/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/
    
    	int on = 1;
    	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    		ERR_EXIT("setsockopt");
    
    	if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    		ERR_EXIT("bind");
    	if (listen(listenfd, SOMAXCONN) < 0)
    		ERR_EXIT("listen");
    
    	struct sockaddr_in peeraddr;
    	socklen_t peerlen = sizeof(peeraddr);
    	int conn;
    
    	pid_t pid;
    	while (1)
    	{
    		if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
    			ERR_EXIT("accept");
    
    		printf("ip=%s port=%d
    ", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    
    		pid = fork();
    		if (pid == -1)
    			ERR_EXIT("fork");
    		if (pid == 0)
    		{
    			close(listenfd);
    			do_service(conn);
    			exit(EXIT_SUCCESS);
    		}
    		else
    			close(conn);
    	}
    	
    	return 0;
    }
    

    客户端:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    struct packet
    {
            int len;
            char buf[1024];
    };
    
    ssize_t readn(int fd, void *buf, size_t count)
    {
            size_t nleft = count;
            ssize_t nread;
            char *bufp = (char*)buf;
    
            while (nleft > 0)
            {
                    if ((nread = read(fd, bufp, nleft)) < 0)
                    {
                            if (errno == EINTR)
                                    continue;
                            return -1;
                    }
                    else if (nread == 0)
                            return count - nleft;
    
                    bufp += nread;
                    nleft -= nread;
            }
    
            return count;
    }
    
    ssize_t writen(int fd, const void *buf, size_t count)
    {
            size_t nleft = count;
            ssize_t nwritten;
            char *bufp = (char*)buf;
    
            while (nleft > 0)
            {
                    if ((nwritten = write(fd, bufp, nleft)) < 0)
                    {
                            if (errno == EINTR)
                                    continue;
                            return -1;
                    }
                    else if (nwritten == 0)
                            continue;
    
                    bufp += nwritten;
                    nleft -= nwritten;
            }
    
            return count;
    }
    int main(void)
    {
    	int sock;
    	if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    		ERR_EXIT("socket");
    
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(5188);
    	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    	if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    		ERR_EXIT("connect");
    
    
    	struct packet sendbuf;
    	struct packet recvbuf;
    	memset(&sendbuf, 0, sizeof(sendbuf));
    	memset(&recvbuf, 0, sizeof(recvbuf));
    	int n;
    	while (fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
    	{
    		n = strlen(sendbuf.buf);
    		sendbuf.len = htonl(n);	
    		writen(sock, &sendbuf, 4+n);
    
    
    		int ret = readn(sock, &recvbuf.len, 4);
                    if (ret == -1)
                            ERR_EXIT("read");
                    else if (ret < 4)
                    {
                            printf("client close
    ");
                            break;
                    }
                    
                    n = ntohl(recvbuf.len);
                    ret = readn(sock, recvbuf.buf, n);
                    if (ret == -1)
                            ERR_EXIT("read");
                    else if (ret < n)
                    {
                            printf("client close
    ");
                            break;
                    }
    
    
    		fputs(recvbuf.buf, stdout);
    		memset(&sendbuf, 0, sizeof(sendbuf));
    		memset(&recvbuf, 0, sizeof(recvbuf));
    	}
    
    	close(sock);
    	
    	return 0;
    }
    

    这种解决粘包的方法特别实在网络中传输的时候十分必要。

  • 相关阅读:
    DOS命令大全(二)
    读取本地Json文件
    微信接入详细流程 分享给好友和朋友圈
    IOS开发中(null)与<null>的处理
    iOS 删除NSString中特定字符
    float类型转对象 对象转float类型(一)
    真机调试出现Could not find Developer Disk Image问题解决办法
    iOS开发融云即时通讯集成详细步骤
    UITableVIew 滚动流畅性优化
    Python装饰器
  • 原文地址:https://www.cnblogs.com/DamonBlog/p/4458627.html
Copyright © 2020-2023  润新知