上海景泰建设有限公司网站,名师工作室网站建设 意义,动画设计专业就业前景和就业方向,小程序商店开发第三圣子 最近学习unix网络编程#xff0c;感觉东西零零碎碎#xff0c;比较混乱。因此决定整理以下#xff0c;发一个小博客。一来可以与大家分享以下#xff0c;二来可以总结提高一下所学的东西。话说#xff1a;竹子为什么长的高#xff0c;因为它喜欢总结阿#xff… 第三圣子 最近学习unix网络编程感觉东西零零碎碎比较混乱。因此决定整理以下发一个小博客。一来可以与大家分享以下二来可以总结提高一下所学的东西。话说竹子为什么长的高因为它喜欢总结阿^_^ 废话不多说了上代码。小弟半路出家入行不深过路大神不喜勿喷阿嘿嘿^_^ 程序是一个基于tcp的 C/S .简单回显功能( 声明以下不要以为注释是英语就说我是在哪里下载的原因是我运行程序 汉字老显示乱码就改成蹩脚英语了 )。 首先是一个自己的库 1 #ifndef MYLIB_H2 #define MYLIB_H3 4 #include stdio.h5 #include stdlib.h6 #include netinet/in.h7 #include sys/socket.h8 #include arpa/inet.h9 #include unistd.h
10 #include string.h
11 #include errno.h
12 #include signal.h
13 #include sys/wait.h
14
15 #define LISTENQ 1024
16 #define MAXLINE 1460
17 #define SERV_PORT 9877
18
19 typedef void (*SignalFunc)(int);
20
21 SignalFunc signal(int sigNo,SignalFunc fun);
22 void sig_chld(int sigNo);
23 void sys_err(char *pa);
24
25 #endif // MYLIB_H 这些是需要的头文件和一些宏定义服务端和客户端都需要我都把他们搞一块儿了这样方便叫mylib.h 。 哦先大致解释一下 1、signal 这个函数是用来捕获信号的。后边服务端会用到在服务端在细说 2、sig_chld 是signal捕获到信号后的处理函数 3、sys_err 用来输出提示并退出进程 下边是头文件里函数的实现里边的函数如果没看太懂可以先不用理解后边会细说哈哈 1 #include mylib.h2 3 void sys_err(char *pa)4 {5 printf(%s,pa);6 exit(1);7 }8 9 SignalFunc signal(int sigNo, SignalFunc fun){
10 struct sigaction act , oact;
11 act.sa_handlerfun;
12 sigemptyset(act.sa_mask); //Additional set of signals to be blocked.
13 act.sa_flags0;
14 if(sigaction(sigNo,act,oact)0)
15 return SIG_ERR;
16 return oact.sa_handler;
17 }
18
19 void sig_chld(int sigNo)
20 {
21 pid_t pid;
22 int state;
23 while ((pidwaitpid(-1,state,WNOHANG))0) {
24 printf(process %d terminated \n,pid);
25 }
26 return;
27 } 下边是客户端代码 1 #include mylib.h2 int main(void)3 {4 int sock_fd;5 sock_fdsocket(AF_INET,SOCK_STREAM,0);6 7 struct sockaddr_in serv_add;8 bzero(serv_add,sizeof(serv_add));9 serv_add.sin_familyAF_INET;
10 serv_add.sin_porthtons(SERV_PORT);
11 struct in_addr add;
12 inet_aton(192.168.1.105,add);
13 serv_add.sin_addradd;
14
15 if(connect(sock_fd,(struct sockaddr *)serv_add,sizeof(serv_add))0)
16 sys_err(connect error!\n);
17 char sendbuff[MAXLINE],recvbuff[MAXLINE];
18 char *temp;
19 ssize_t n;
20 while ((tempfgets(sendbuff,sizeof(sendbuff),stdin)) !NULL)
21 {
22 int k;
23 if((kwrite(sock_fd,sendbuff,sizeof(sendbuff)))0)
24 {
25
26 }
27 if((nread(sock_fd,recvbuff,sizeof(recvbuff)))0)
28 printf(SVR:%s,recvbuff);
29 if(n0)
30 printf(fail to get data from server %s\n,inet_ntoa(serv_add.sin_addr));
31 if(n0)
32 {
33 //broken pipe haha sigpipe
34 printf(%s,server defunct\nclosing the socket...);
35 close(sock_fd);
36 }
37 }
38 return 0;
39 } 解释一下客户端代码 第5行sock_fdsocket(AF_INET,SOCK_STREAM,0); 用socket函数创建一个sock第一个参数是协议族我们用AF_INET代表tcp/ip协议族。第二个参数代表流方式也就是TCP 字节流方式。第三个参数,额...貌似有点高深说实话不懂注释说 If it is zero, is chosen automatically. 就是自动选择我擦这一自动我感觉整个人都不舒服了... 行7struct sockaddr_in serv_add; 定义一个sock 地址结构用来存放服务器断ip端口协议族之类的。 行1012这两行里都有一个来处理端口和ip为啥这里牵扯一个“字节序”的问题处理器对字节的排列顺序不是相同的这个可以百度以下呵呵 行15用本地初始化的sock和服务端地质结构建立连接。等等为啥客户端sock没有地址和端口呢怎么直接就连接了这不科学。额就这在这一不tcp默认将本地sock地址设为本地ip端口在允许范围内随机取值一般不会是vip端口(1024)啦。而且每次链接都会随机端口。好地址设好就可以连接服务器了进行关键的三次握手。 行2023从输入设备读取 输入值。写入打开的 socket 文件符。将输入值 写入建好的 pipe里。这里的write为什么回有小于0的情况呢原因是当服务起进程断开连接或者不小心关闭时服务端乎发给客户端一个FIN表示终止链接但是由于TCP是半关闭的客户端可能正在输入不知道服务端已经断开了服务端TCP会返回一个RST告诉客户端管道不通。这时如果继续将值写入pipe系统就会立马提示你 管道断裂发一个SIGPIPE信号给你。这个信号很要命啊你不捕获处理系统就默认关掉你的进程。处理了write就返回小于0 行27读取服务器的返回值读取失败就返回小于0 行31同样服务断断开后客户端已收到FIN的通知read后直接返回0. 这里为了不让出现23行的问题干脆把socket关闭了 下边是服务端代码 1 #include mylib.h2 3 int main(void)4 {5 int listen_fd , connected_fd;6 struct sockaddr_in serv_add;7 //-----------------------------------------------------------------------------8 listen_fdsocket(AF_INET,SOCK_STREAM,0);9 serv_add.sin_familyAF_INET;
10 serv_add.sin_porthtons(SERV_PORT);
11 serv_add.sin_addr.s_addrhtonl(INADDR_ANY);
12 //------------------------------------------------------------------------------
13 if(bind(listen_fd,(struct sockaddr *)serv_add,sizeof(serv_add))0)
14 sys_err(bind error\n);
15 if(listen(listen_fd,LISTENQ)0)
16 sys_err(listen error\n);
17
18 signal(SIGCHLD,sig_chld);
19
20 __pid_t pid;
21 while (1) {
22 connected_fd accept(listen_fd,0,0);
23 if(connected_fd0){
24 if(errnoEINTR)
25 {
26 printf(interrupt\n);
27 continue;
28 }
29 else
30 sys_err(accept eero!\n);
31 }
32 if((pidfork())0)
33 {
34 char recevBuff[MAXLINE];
35 int n;
36 close(listen_fd);
37 while ((nread(connected_fd,recevBuff,MAXLINE))0) {
38 printf(Client:%s,recevBuff);
39 write(connected_fd,recevBuff,MAXLINE);
40 }
41 if(n0)
42 sys_err(read error\n);
43 close(connected_fd);
44 exit(0);
45 }
46 close(connected_fd);
47 }
48 return 0;
49 } 服务端和客户端代码有一些相似的地方。说一下不一样的地方把。打字打的手要抽了都。 行11意思是通配本主机上所有的网络接口(如果有多个的话).就是不管哪个接口受到请求都去处理连接 行13把sock文件符绑定到指定的地址和端口上形成一个完整sock。 行15服务端sock打开侦听文件符不同的sock过来建立连接是要排队的第二个参数控制最大排队的数量毕竟缓冲区是有限的 行22服务端进入侦听后回阻塞在accept如果一个tcp三次握手成功了就打开一个accepted_fd并建立一个通道。继续往下走 行23链接错误或者链接被内核中断就会返回小于0因为这时进程处在一个可被中断的睡眠状态。如果进程接到要去处理进程的通知这个睡眠会被唤醒而且 内核不一定就回重启这个等待不重启的时候就回返回一个errnoEINTR(error interrupt),这时我们就重新启动这个等待 , continue 行32这里有个比较重要的函数 fork它是系统唯一能创造分支进程的方法,就是子进程。 QA-01为什么这里要开进程。 A因为如果有多个链接连入服务器的话一个进程肯定忙不过来阿这样就会导致很对在那排队等待有的甚至连不上因为服务起很忙。 QA-02if((pidfork())0) 为什么这里这么写呢因为 这个fork函数很特别调用一次会返回两个值一个是子进程的pid一个是0 。类似于一个链表格式 ppid | pid |chldpid ,子进程pid在父进程里返回子进程就返回0 。进入子进程后父进程的所有文件符都会复制到子进程的上下文是的是复制。子进程对文件符的操作不会影响父进程父进程也不会影响子进程。子进程执行完毕后必须 退出否则的话 可能会继续fork子进程死循环。 这样以来每次成功建立连接都会有一个独立的进程去处理他们的数据交流不会阻塞在父进程就完成了 并发处理。 行43这里为什么要关闭 connected fd呢因为如果不关闭的话每来一个链接都会新建一个fd(file describe)少年内核里进程表表项里存储文件符的数组大小可是有限的。这里子进程也会关掉从父进程复制来的文件符这个文件符是有计数的称谓共享当计数恢复0时文件符就关了。 最后说行18捕获信号。 当这些个子进程都完成自己任务后 ( 也就是客户端断了之后 )不会自动退出内核。而是变成了 defunct 状态挂掉了。木错是挂掉了 为什么儿子们都挂掉了老爹不来收尸呢 这个原因貌似是比较复杂因为子进程结束了要通知父进程一些关于自己执行情况的数据 。父进程默认是忽略的等父进程结束的时候这些 僵死进程就会 被只给 进程 1 init他恢复则处理这些 挂掉的进程。 但是我们的服务起 肯定不想让这些 挂掉的进程 挤满内存占据资源于是就在 行18 捕获子进程发来的信号 SIGCHLD 然后进程如果接到信号就会从睡眠中苏醒去 wait 它这个函数很特别他会负责处理掉这些挂掉的进程。 好吧服务端是比较复杂这里代码肯定是有很多缺陷的。一个服务要想跑起来 要考虑非常多的突发情况攻击神马的这个小程序只是打通通信过程呵呵 写到这里我又凌乱了.....睡觉 机智的少年 估计去开发局域网聊天程序了 o(∩_∩)o... 哦忘了上截图sorry 所谓无图无真相 转载于:https://www.cnblogs.com/DiSanShengZi/p/4041599.html