zoukankan      html  css  js  c++  java
  • linux下的socket通信小程序分享——第三圣子

    第三圣子

    最近学习unix网络编程,感觉东西零零碎碎,比较混乱。因此决定整理以下,发一个小博客。一来可以与大家分享以下,二来可以总结提高一下所学的东西。话说:竹子为什么长的高,因为它喜欢总结阿~~^_^

    废话不多说了,上代码。小弟半路出家,入行不深,过路大神不喜勿喷阿,嘿嘿~~^_^

    程序是一个基于tcp的 C/S .简单回显功能( 声明以下,不要以为注释是英语就说我是在哪里下载的,原因是我运行程序 汉字老显示乱码,就改成蹩脚英语了 )。

    首先是一个自己的库

     1 #ifndef MYLIB_H
     2 #define MYLIB_H
     3 
     4 #include <stdio.h>
     5 #include <stdlib.h>
     6 #include <netinet/in.h>
     7 #include <sys/socket.h>
     8 #include <arpa/inet.h>
     9 #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.h>
     2 
     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_handler=fun;
    12     sigemptyset(&act.sa_mask);    //Additional set of signals to be blocked.
    13     act.sa_flags=0;    
    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 ((pid=waitpid(-1,&state,WNOHANG))>0) {
    24         printf("process %d terminated 
    ",pid);
    25     }
    26     return;
    27 }

    下边是客户端代码:

     1 #include <mylib.h>
     2 int main(void)
     3 {
     4     int sock_fd;
     5     sock_fd=socket(AF_INET,SOCK_STREAM,0);
     6 
     7     struct sockaddr_in serv_add;
     8     bzero(&serv_add,sizeof(serv_add));
     9     serv_add.sin_family=AF_INET;
    10     serv_add.sin_port=htons(SERV_PORT);
    11     struct in_addr add;
    12     inet_aton("192.168.1.105",&add);
    13     serv_add.sin_addr=add;
    14 
    15     if(connect(sock_fd,(struct sockaddr *)&serv_add,sizeof(serv_add))<0)
    16         sys_err("connect error!
    ");
    17     char sendbuff[MAXLINE],recvbuff[MAXLINE];
    18     char *temp;
    19     ssize_t n;
    20     while ((temp=fgets(sendbuff,sizeof(sendbuff),stdin)) !=NULL)
    21     {
    22         int k;
    23         if((k=write(sock_fd,sendbuff,sizeof(sendbuff)))<0)
    24         {
    25 
    26         }
    27         if((n=read(sock_fd,recvbuff,sizeof(recvbuff)))>0)
    28             printf("SVR:%s",recvbuff);
    29         if(n<0)
    30             printf("fail to get data from server %s
    ",inet_ntoa(serv_add.sin_addr));
    31         if(n==0)
    32         {
    33             //broken pipe ,haha  sigpipe
    34             printf("%s","server defunct
    closing the socket...");
    35             close(sock_fd);
    36         }
    37     }
    38     return 0;
    39 }

    解释一下客户端代码:

      第5行:sock_fd=socket(AF_INET,SOCK_STREAM,0); 用socket函数创建一个sock,第一个参数是协议族,我们用AF_INET代表tcp/ip协议族。第二个参数代表流方式,也就是TCP 字节流方式。第三个参数,额...貌似有点高深,说实话,不懂,注释说 If it is zero,  is chosen automatically.  就是自动选择,我擦,这一自动,我感觉整个人都不舒服了...

      行7:struct sockaddr_in serv_add;  定义一个sock 地址结构,用来存放服务器断ip,端口,协议族之类的。

      行10,12:这两行里都有一个来处理端口和ip,为啥,这里牵扯一个“字节序”的问题,处理器对字节的排列顺序不是相同的,这个可以百度以下,呵呵

      行15:用本地初始化的sock和服务端地质结构建立连接。等等,为啥客户端sock没有地址和端口呢,怎么直接就连接了,这不科学。额,就这在这一不,tcp默认将本地sock地址设为本地ip,端口在允许范围内随机取值,一般不会是vip端口(<1024)啦。而且每次链接都会随机端口。好,地址设好就可以连接服务器了,进行关键的三次握手。

      行20,23:从输入设备读取 输入值。写入打开的 socket 文件符。将输入值 写入建好的 pipe里。这里的write为什么回有小于0的情况呢,原因是,当服务起进程断开连接,或者不小心关闭时,服务端乎发给客户端一个FIN,表示终止链接,但是由于TCP是半关闭的,客户端可能正在输入,不知道服务端已经断开了,服务端TCP会返回一个RST,告诉客户端管道不通。这时如果继续将值写入pipe,系统就会立马提示你 管道断裂,发一个SIGPIPE信号给你。这个信号很要命啊,你不捕获处理,系统就默认关掉你的进程。处理了,write就返回小于0

      行27:读取服务器的返回值,读取失败就返回小于0

      行31:同样,服务断断开后,客户端已收到FIN的通知,read后直接返回0. 这里为了不让出现23行的问题,干脆把socket关闭了

      

    下边是服务端代码:

     1 #include <mylib.h>
     2 
     3 int main(void)
     4 {
     5     int listen_fd , connected_fd;
     6     struct sockaddr_in serv_add;
     7 //-----------------------------------------------------------------------------
     8     listen_fd=socket(AF_INET,SOCK_STREAM,0);
     9     serv_add.sin_family=AF_INET;
    10     serv_add.sin_port=htons(SERV_PORT);
    11     serv_add.sin_addr.s_addr=htonl(INADDR_ANY);
    12 //------------------------------------------------------------------------------
    13     if(bind(listen_fd,(struct sockaddr *)&serv_add,sizeof(serv_add))<0)
    14         sys_err("bind error
    ");
    15     if(listen(listen_fd,LISTENQ)<0)
    16         sys_err("listen error
    ");
    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_fd<0){
    24         if(errno==EINTR)
    25         {
    26             printf("interrupt
    ");
    27             continue;
    28         }
    29         else
    30             sys_err("accept eero!
    ");
    31        }
    32         if((pid=fork())==0)
    33         {
    34             char recevBuff[MAXLINE];
    35             int n;
    36             close(listen_fd);
    37             while ((n=read(connected_fd,recevBuff,MAXLINE))>0) {
    38                 printf("Client:%s",recevBuff);
    39                 write(connected_fd,recevBuff,MAXLINE);
    40             }
    41             if(n<0)
    42                 sys_err("read error
    ");
    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,因为这时进程处在一个可被中断的睡眠状态。如果进程接到要去处理进程的通知,这个睡眠会被唤醒,而且 内核不一定就回重启这个等待,不重启的时候就回返回一个errno=EINTR(error interrupt),这时,我们就重新启动这个等待 ,  continue

      行32:这里有个比较重要的函数 fork,它是系统唯一能创造分支进程的方法,就是子进程。

    QA-01:为什么这里要开进程。 A:因为如果有多个链接连入服务器的话,一个进程肯定忙不过来阿,这样就会导致很对在那排队等待,有的甚至连不上,因为服务起很忙。

    QA-02:if((pid=fork())==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 ,所谓无图无真相:

  • 相关阅读:
    封装tip控件
    Javascirpt中创建对象的几种方式
    使用Servlet上传文件
    Struts2 基本配置
    使用JQuery实现手风琴布局
    winform下自绘提示框风格窗体
    环形进度条
    Oracle中获取当前时间半小时前的时间
    JSTL+MyEclipse8.5+Tomcat配置
    使用CSS和jQuery实现对话框
  • 原文地址:https://www.cnblogs.com/DiSanShengZi/p/4041599.html
Copyright © 2011-2022 走看看