博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UNIX网络编程卷1:套接字联网-第5章:TCP客户/服务器程序示例
阅读量:4212 次
发布时间:2019-05-26

本文共 5503 字,大约阅读时间需要 18 分钟。

1. 简单回射客户/服务器图解

2.代码实例

以下程序摘自UNIX网络编程,我会对其加上一些注解,方便阅读

TCP回射服务器程序:main函数

/*怎么编译运行可以查看我的另一篇博文:

/* tcpserv01.c */   #include 
#include
#include
#include
#include
#include
#include
#include
intmain(int argc, char **argv){ int listenfd, connfd; //用来存储监听套接字fd和连接套接字fd pid_t childpid; // 用来存储子进程fd socklen_t clilen; //用来存储套接字地址大小 struct sockaddr_in cliaddr, servaddr; //用来存储服务端和客户端套接字地址 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //创建使用ipv4,tcp的流套接字 { perror("socket"); exit(1); } bzero(&servaddr, sizeof(servaddr)); //初始化服务端套接字地址为0,bzero已经过时,此处最好使用memset servaddr.sin_family = AF_INET; //使用ipv4协议族 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //将主机字节序转换为网络字节序(默认大端字节序)并赋予相应值,INADDR_ANY表示接受任意地址的请求 servaddr.sin_port = htons(9877); //赋予服务端套接字地址结构中的端口值 if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) //将创建的套接字listenfd和服务端套接字地址结构绑定在一起 { perror("bind"); exit(1); } if(listen(listenfd, 5) < 0) //监听端口收到的请求,最大连接数设置为5,调用listen后内核会维护两个队列,具体参见 { perror("listen"); exit(1); } for(;;) //最常见的服务进程是被动的等待请求,除非手动关闭或者遇到异常,否则不会主动关闭 { clilen = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) //若已连接队列为空,则服务进程阻塞于此,否则取出队列头,内核生成一个全新的描述符,cliaddr是值-结果参数 { perror("accept"); exit(1); } if((childpid = fork()) < 0) //毫无悬念,创建子进程。注意,创建子进程后,对父进程现有套接字的引用都增1,表示子进程也可使用父进程拥有的套接字,此时注意close一些不是用的 { perror("fork"); exit(1); } else if(childpid == 0) /* child process */ fork返回两次,返回0的表示处于子进程 { if(close(listenfd) < 0) /* close listening socket */ /*关掉子进程对监听套接字的可使用权,监听套接字引用计数减1,直至计数为0,才回真正关闭套接字*/ { perror("child close"); exit(1); } str_echo(connfd); /* process the request */ //传递连接套接字给服务器程序的业务处理函数,从而进行通信操作 exit(0); /*此处别忘了哟,子进程用完了要退出,要不然站着进程资源不释放,又没用,你猜会怎么样 } if(close(connfd) < 0) /* parent close connected socket */ //父进程也要关闭连接套接字,(以为accept成功返回的时候,父进程也拥有了对连接套接字的使用权) { perror("parent close"); exit(1); } }}

TCP回射服务器程序:str_echo函数

/* str_echo.c */#include 
#include
#include
voidstr_echo(int sockfd){ ssize_t n; char buf[4096]; //创建一个应用层缓冲区,用于存放从套接字中读到的数据again: while((n = read(sockfd, buf, 4096)) > 0)//如果套接字队列没有数据可读,则read处于阻塞状态 writen(sockfd, buf, n); if(n < 0 && errno == EINTR) //read被信号中断,此时重新读 goto again; else if(n < 0) //read发生错误,打印错误信息,并终止进程 { perror("read"); exit(1); } }

TCP回射客户程序:main函数

/* tcpcli01.c */#include 
#include
#include
#include
#include
#include
#include
#include
#include
intmain(int argc, char **argv){ int sockfd; struct sockaddr_in servaddr; if(argc != 2) { printf("usage: tcpcli
"); exit(0); } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) //不管是服务程序还是客户程序,想要网络通信,都得先创建通信用的网络套接字 { perror("socket"); //创建套接字失败的下场 exit(1); } bzero(&servaddr, sizeof(servaddr)); //同样的,初始化套接字地址结构为0 servaddr.sin_family = AF_INET; servaddr.sin_port = htons(9877); if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0) { perror("inet_pton"); exit(1); } if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) //调用connect会发起三路握手的连接过程 { perror("connect"); exit(1); } str_cli(stdin, sockfd); /* do it all */ //调用客户端的业务处理程序,传入标准输入流(用于得到客户端的输入)和用于网络通信的套接字描述符 exit(0);}

TCP回射客户程序:str_cli函数

/* str_cli.c */#include 
#include
#include
voidstr_cli(FILE *fp, int sockfd){ char sendline[4096], recvline[4096]; //应用程序创建一个发送缓冲区和接收缓冲区 while(fgets(sendline, 4096, fp) != NULL) // { writen(sockfd, sendline, strlen(sendline)); if(readline(sockfd, recvline, 4096) == 0) { printf("str_cli: server terminated prematurely"); exit(0); } fputs(recvline, stdout); }}

当然,上述程序还有诸多问题,我们只是以一个简单示例程序来展示tcp客户端和服务通信

改进的服务器程序:(增加了清除僵尸进程的机制和accept被中断重新调用的机制)

#include    "unp.h"            int      main(int argc, char **argv)      {          int                 listenfd, connfd;          pid_t               childpid;          socklen_t           clilen;          struct sockaddr_in  cliaddr, servaddr;          void                sig_chld(int);                listenfd = Socket(AF_INET, SOCK_STREAM, 0);                bzero(&servaddr, sizeof(servaddr));          servaddr.sin_family      = AF_INET;          servaddr.sin_addr.s_addr = htonl(INADDR_ANY);          servaddr.sin_port        = htons(SERV_PORT);                Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));                Listen(listenfd, LISTENQ);                Signal(SIGCHLD, sig_chld);  /* must call waitpid() */                for ( ; ; ) {              clilen = sizeof(cliaddr);              if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {                  if (errno == EINTR)                      continue;       /* back to for() */                  else                      err_sys("accept error");              }                    if ( (childpid = Fork()) == 0) {    /* child process */                  Close(listenfd);    /* close listening socket */                  str_echo(connfd);   /* process the request */                  exit(0);              }              Close(connfd);          /* parent closes connected socket */          }      }

void sig_chld(int signo)  {      pid_t pid;      int stat;        pid = wait(&stat);    //      printf("child %d terminated\n",pid);         return;  }

你可能感兴趣的文章
使用HttpServletResponseWrapper获取渲染jsp以后的html
查看>>
Tomcat的ThreadLocalLeakPreventionListener工作原理
查看>>
利用springMVC的interceptor实现页面性能监控(Filter亦可)
查看>>
SpringMVC 拦截器实现分析
查看>>
初始化(Map,List)容器类的容量会有一定的性能提升
查看>>
StringBuffer与StringBuilder浅析
查看>>
BoneCP数据源记录SQl比hibernate的show sql好用
查看>>
对Cookie的一点认识
查看>>
说一说hibernate的Get和Load
查看>>
如何修改tomcat的server信息增加服务器的安全
查看>>
浅谈tomcat的ThreadLocalLeakPreventionListener实现原理
查看>>
说一下多线程中用到的join
查看>>
扩展hibernate Criteria的Order使其支持sql片段(oracle)
查看>>
spring+mybatis利用interceptor(plugin)实现数据库读写分离
查看>>
NIO[SelectableChannel.register和Selector.select会有锁等待冲突]
查看>>
httpclient3.1的relaseConnection的misunderstand
查看>>
ReentrantLock为啥会出现不公平的场景
查看>>
图解LinkedHashMap的LRU
查看>>
关于select()方法最大轮询数限制的更正
查看>>
话说Connect reset异常
查看>>