socket编程入门(三)
来源:互联网 发布:常德跑胡子源码 编辑:程序博客网 时间:2024/05/22 12:52
下面给出开发简单客户回射服务器程序迭代过程。
第一版
程序见上一篇博客末尾,由于服务器关闭后处于TIMEWAIT状态,因此存在一个问题:短时间内服务器程序不能重启并使用。
第二版(服务器端使用了REUSEADDR选项)
使用这个选项后,即使服务器处于TIMEWAIT状态,也可以重新启用。
下面给出实现代码。(默认makefile就是上篇博客末尾的makefile)
cli:同第一版的cli程序。
srv:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)int main(int argc,char* argv[]){ int listenfd; if((listenfd = 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 = 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; if((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0) ERR_EXIT("accept"); printf("ip=%s portr=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); char recvbuf[1024]; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(conn,recvbuf,sizeof(recvbuf)); fputs(recvbuf,stdout); write(conn,recvbuf,ret); } return 0;}
第三版(增加并发)
父子进程做处理:父进程接受accept,子进程处理连接。
(cli和makefile同第二版)
srv:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)void do_service(int fd);ssize_t readn(int fd,void* buf,size_t count);int main(int argc,char* argv[]){ int listenfd; if((listenfd = 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 = 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 portr=%d\n",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); /*break out then close child process*/ exit(EXIT_SUCCESS); }else { close(conn);/*parent and child share the fds*/ } } return 0;}void do_service(int fd){ char recvbuf[1024]; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(fd,recvbuf,sizeof(recvbuf)); if(ret == 0)/*client has closed*/ { printf("client closed\n"); break; }else if(ret == -1) { ERR_EXIT("read"); } fputs(recvbuf,stdout); write(fd,recvbuf,ret); }}
第四版 (增加对粘包的处理)
粘包来源
* TCP协议本身(传输层)不能传递消息边界,原因在于基于字节流的TCP是不能保证对等方一次读操作返回多少字节的,因而存在粘包问题。
处理粘包的常见解决办法:
* 定长包
* 包尾加\r\n(ftp)
* 包头加上包体长度
第一次尝试
先尝试定长包的方式来完善之前的第三版程序,思路是封装readn和writen函数。
代码
cli:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)ssize_t readn(int fd,void* buf,size_t count);ssize_t writen(int fd,void* buf,size_t count);int main(int argc,char* argv[]){ 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 = htonl("127.0.0.1"); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("connect"); char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { writen(sock,sendbuf,sizeof(sendbuf)); readn(sock,recvbuf,sizeof(recvbuf)); fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } return 0;}ssize_t readn(int fd,void* buf,size_t count){ size_t nleft = count; size_t nread = 0; 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,void* buf,size_t count){ size_t nleft = count; size_t nwritten = 0; 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;}
srv:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)void do_service(int fd);ssize_t readn(int fd,void* buf,size_t count);ssize_t writen(int fd,void* buf,size_t count);int main(int argc,char* argv[]){ int listenfd; if((listenfd = 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 = 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 portr=%d\n",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); /*break out then close child process*/ exit(EXIT_SUCCESS); }else { close(conn);/*parent and child share the fds*/ } } return 0;}void do_service(int fd){ char recvbuf[1024]; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = readn(fd,recvbuf,sizeof(recvbuf)); if(ret == 0)/*client has closed*/ { printf("client closed\n"); break; }else if(ret == -1) { ERR_EXIT("read"); } fputs(recvbuf,stdout); writen(fd,recvbuf,ret); }}ssize_t readn(int fd,void* buf,size_t count){ size_t nleft = count;/*count:bytes which need to read*/ ssize_t nread;/*has readed*/ char* bufp = (char*)buf; while(nleft > 0) { if((nread = read(fd,bufp,nleft)) < 0) { /*if signal interrupt*/ if(errno == EINTR) continue; return -1; } else if(nread == 0)/*peer has been closed*/ { // break; return count - nleft; } bufp += nread; nleft -= nread; } return count;}ssize_t writen(int fd,void* buf,size_t count){ size_t nleft = count;/*count:bytes which need to read*/ ssize_t nwritten;/*has readed*/ char* bufp = (char*)buf; while(nleft > 0) { if((nwritten = write(fd,bufp,nleft)) < 0) { /*if signal interrupt*/ if(errno == EINTR) continue; return -1; } else if(nwritten == 0)/*peer has been closed*/ { continue; } bufp += nwritten; nleft -= nwritten; } return count;}
定长包虽然可以解决粘包问题,但是其每次发生都需要一个缓冲区,有效数据可能在其中所占比重非常小,举一个极端例子,每次只发送一个字符。每次发送数据时,大量无效数据充斥于网络,无形中增大了网络负担,因而还需要进一步改进。
第二次尝试
增加一个包头,包头存放每次传输的数据长度。
这样可舍弃定长包。
代码
cli:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)typedef struct _Package{ int len; char buf[1024];}Package;ssize_t readn(int fd,void* buf,size_t count);ssize_t writen(int fd,void* buf,size_t count);int main(int argc,char* argv[]){ 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 = htonl("127.0.0.1"); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("connect"); Package sendbuf; Package recvbuf; memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); int n = 0; while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL) { n = strlen(sendbuf.buf); sendbuf.len = htonl(n); writen(sock,&sendbuf,4 + n); /*receive head len field*/ int ret = readn(sock,&recvbuf.len,4); if(ret == -1)ERR_EXIT("read"); else if(ret < 4) { printf("srv close\n"); break; } n = ntohl(recvbuf.len); ret = readn(sock,recvbuf.buf,n); if(ret == -1)ERR_EXIT("read"); else if(ret < n) { printf("srv close\n"); break; } fputs(recvbuf.buf,stdout); memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); } close(sock); return 0;}ssize_t readn(int fd,void* buf,size_t count){ size_t nleft = count; size_t nread = 0; 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,void* buf,size_t count){ size_t nleft = count; size_t nwritten = 0; 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;}
srv:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)typedef struct _Package{ int len; char buf[1024];}Package;void do_service(int fd);ssize_t readn(int fd,void* buf,size_t count);ssize_t writen(int fd,void* buf,size_t count);int main(int argc,char* argv[]){ int listenfd; if((listenfd = 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 = 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 portr=%d\n",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); /*break out then close child process*/ exit(EXIT_SUCCESS); }else { close(conn);/*parent and child share the fds*/ } } return 0;}void do_service(int fd){ Package recvbuf; //char recvbuf[1024]; memset(&recvbuf,0,sizeof(recvbuf)); int n = 0; while(1) { memset(&recvbuf,0,sizeof(recvbuf)); int ret = readn(fd,&recvbuf.len,4); if(ret == -1)ERR_EXIT("read"); else if(ret < 4)/*client has closed*/ { printf("client closed\n"); break; } n = ntohl(recvbuf.len); ret = readn(fd,recvbuf.buf,n); if(ret == -1)ERR_EXIT("read"); else if(ret < n)/*client has closed*/ { printf("client closed\n"); break; } fputs(recvbuf.buf,stdout); writen(fd,&recvbuf,n + 4); }}ssize_t readn(int fd,void* buf,size_t count){ size_t nleft = count;/*count:bytes which need to read*/ ssize_t nread;/*has readed*/ char* bufp = (char*)buf; while(nleft > 0) { if((nread = read(fd,bufp,nleft)) < 0) { /*if signal interrupt*/ if(errno == EINTR) continue; return -1; } else if(nread == 0)/*peer has been closed*/ { // break; return count - nleft; } bufp += nread; nleft -= nread; } return count;}ssize_t writen(int fd,void* buf,size_t count){ size_t nleft = count;/*count:bytes which need to read*/ ssize_t nwritten;/*has readed*/ char* bufp = (char*)buf; while(nleft > 0) { if((nwritten = write(fd,bufp,nleft)) < 0) { /*if signal interrupt*/ if(errno == EINTR) continue; return -1; } else if(nwritten == 0)/*peer has been closed*/ { continue; } bufp += nwritten; nleft -= nwritten; } return count;}
0 0
- socket编程入门(三)
- socket编程(三)
- socket编程(三)
- socket编程(三)
- iOS socket编程(入门)
- socket编程入门(一)
- socket编程入门(二)
- Java Socket编程(三)
- Java Socket编程(三)
- Java socket编程(三)
- Java(三) Socket编程
- socket 编程基础(三)
- linux socket 编程(三)
- Socket 基础编程(三)
- JavaSE入门学习49:Socket网络通信编程(三)
- JavaSE入门学习49:Socket网络通信编程(三)四通过Socket实现TCP编程
- shell编程入门(三)
- Linux socket编程入门(1)
- SVN Unable to connect to a repository at URL
- Spring结合Swagger实现API管理
- 硬链接和软连接
- 训练营第七 -- 十天学习笔记
- Servlet过滤器
- socket编程入门(三)
- 数据结构(C语言)学习之路——算法
- 最长递增子序列
- 宅急送 项目第五天 区域、分区、定区
- java8:lambda表达式强化练习
- CentOS 6.5通过命令行安装发送邮件
- Halcon实例教程之金属表面划痕及裂缝检测
- HashMap源码阅读笔记
- 集合框架-Arrays工具类的asList()方法(把数组转成集合)