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 \n",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!\n");
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\n",inet_ntoa(serv_add.sin_addr));
31         if(n==0)
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_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\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_fd<0){
24         if(errno==EINTR)
25         {
26             printf("interrupt\n");
27             continue;
28         }
29         else
30             sys_err("accept eero!\n");
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\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,因为这时进程处在一个可被中断的睡眠状态。如果进程接到要去处理进程的通知,这个睡眠会被唤醒,而且 内核不一定就回重启这个等待,不重启的时候就回返回一个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 ,所谓无图无真相:

 

转载于:https://www.cnblogs.com/DiSanShengZi/p/4041599.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/472887.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

处理器指令编码可重定义的方法_从零开始设计四位栈处理器(2)——结构与指令集...

从零设计四位栈处理器&#xff08;2&#xff09;——结构与指令集一句话概括&#xff1a; 在Toxic处理器中&#xff0c;万物皆栈。熟悉汇编语言的同学会了解&#xff0c;一般的汇编语言&#xff0c;会包含以下几个部分&#xff1a;寄存器地址立即数操作码在这期文章中&#xff…

raptor累乘流程图_Markdown快速上手指南

Markdown快速上手指南1、Markdown介绍markdown可以实现快速html文档编辑&#xff0c;格式优没&#xff0c;并且不需要使用html元素。 markdown采用普通文本的形式&#xff0c;例如读书笔记等易于使用的文本格式进行编写。 如果实在需要生成markdown不支持的html元素的话&#x…

LeetCode 1764. 通过连接另一个数组的子数组得到一个数组

文章目录1. 题目2. 解题1. 题目 给你一个长度为 n 的二维整数数组 groups &#xff0c;同时给你一个整数数组 nums 。 你是否可以从 nums 中选出 n 个 不相交 的子数组&#xff0c;使得第 i 个子数组与 groups[i] &#xff08;下标从 0 开始&#xff09;完全相同&#xff0c;…

db文件怎么修改_MongoDB最新4.2.7版本三分片集群修改IP实操演练

背景重新组网&#xff0c;需要对现有MongoDB分片集群服务器的IP进行更改&#xff0c;因此也需要对MongoDB分片集群的IP也进行相应的更新&#xff0c;而MongoDB分片集群的IP修改不能单纯的通过配置来进行&#xff0c;需要一番折腾后才能正常更新&#xff0c;这里对整个MongoDB集…

cron 每周一执行_详解定时任务中的 cron 表达式

1.前言 我们经常使用 cron 表达式来定义定时任务的执行策略,今天我们就总结一下 cron 表达式的一些相关知识。 2. cron 表达式的定义 cron 表达式是一个字符串,该字符串由 6 个空格分为 7 个域,每一个域代表一个时间单位。格式如下: [秒] [分] [时] [日] [月] [周] [年] 通…

OGEngine教程:声音载入

以下介绍声音资源从载入到播放的一个流程 首先&#xff0c;我们将须要的音频文件放到assets文件夹下&#xff0c;OGE中SoundRes和MusicRes为我们封装了非常多经常使用的方法&#xff0c;能够用于载入及播放等经常使用功能。 载入 //设置声音读取路径 Device.getDevice().getSou…

LeetCode 1765. 地图中的最高点(BFS)

文章目录1. 题目2. 解题1. 题目 给你一个大小为 m x n 的整数矩阵 isWater &#xff0c;它代表了一个由 陆地 和 水域 单元格组成的地图。 如果 isWater[i][j] 0 &#xff0c;格子 (i, j) 是一个 陆地 格子。 如果 isWater[i][j] 1 &#xff0c;格子 (i, j) 是一个 水域 格…

LeetCode 1768. 交替合并字符串

文章目录1. 题目2. 解题1. 题目 给你两个字符串 word1 和 word2 。 请你从 word1 开始&#xff0c;通过交替添加字母来合并字符串。 如果一个字符串比另一个字符串长&#xff0c;就将多出来的字母追加到合并后字符串的末尾。 返回 合并后的字符串 。 示例 1&#xff1a; 输入…

.git文件夹_Git幸存者指南

> Learn how to use Git to version control a cake recipe… and other things like code!或如何用Git烤蛋糕Git很难。 Git令人生畏。 学习曲线很大。 作为软件工程师&#xff0c;这至关重要。Git是用于版本控制的行业标准。 这是我们大多数人在学校或编码训练营中都不学的…

mysql偏移注入_移位溢注:告别靠人品的偏移注入

*本文原创作者&#xff1a;SeagullGR&#xff0c;本文属FreeBuf原创奖励计划&#xff0c;未经许可禁止转载在Access数据库类型注入的时候&#xff0c;我们获取不到列名(前提是有表名)&#xff0c;一般会选择使用偏移注入&#xff0c;但是这种注入方式往往借助的是个人的人品&am…

LeetCode 1769. 移动所有球到每个盒子所需的最小操作数(前缀和)

文章目录1. 题目2. 解题1. 题目 有 n 个盒子。给你一个长度为 n 的二进制字符串 boxes &#xff0c;其中 boxes[i] 的值为 0 表示第 i 个盒子是 空 的&#xff0c;而 boxes[i] 的值为 1 表示盒子里有 一个 小球。 在一步操作中&#xff0c;你可以将 一个 小球从某个盒子移动到…

python3知识点汇总_35个高级Python知识点总结

No.1 一切皆对象 众所周知&#xff0c;Java中强调“一切皆对象”&#xff0c;但是Python中的面向对象比Java更加彻底&#xff0c;因为Python中的类(class)也是对象&#xff0c;函数&#xff08;function&#xff09;也是对象&#xff0c;而且Python的代码和模块也都是对象。 Py…

LeetCode 1770. 执行乘法运算的最大分数(DP)

文章目录1. 题目2. 解题1. 题目 给你两个长度分别 n 和 m 的整数数组 nums 和 multipliers &#xff0c;其中 n > m &#xff0c;数组下标 从 1 开始 计数。 初始时&#xff0c;你的分数为 0 。 你需要执行恰好 m 步操作。在第 i 步操作&#xff08;从 1 开始 计数&#x…

统计--过滤(筛选)索引的统计信息过期问题测试

基础知识普及&#xff1a; 对于筛选索引&#xff0c;MSDN如是说&#xff1a; 筛选索引是一种经过优化的非聚集索引&#xff0c;尤其适用于涵盖从定义完善的数据子集中选择数据的查询。 筛选索引使用筛选谓词对表中的部分行进行索引。 与全表索引相比&#xff0c;设计良好的筛选…

IDEA连接mysql出现时区错误_idea连接数据库时区错误

错误界面IDEA连接mysql&#xff0c;地址&#xff0c;用户名&#xff0c;密码&#xff0c;数据库名&#xff0c;全都配置好了&#xff0c;点测试连接&#xff0c;咔&#xff01;不成功&#xff01;界面是这样的&#xff0c;翻译过来就是&#xff1a;服务器返回无效时区。进入“高…

LeetCode 1771. 由子序列构造的最长回文串的长度(最长回文子序)

文章目录1. 题目2. 解题1. 题目 给你两个字符串 word1 和 word2 &#xff0c;请你按下述方法构造一个字符串&#xff1a; 从 word1 中选出某个 非空 子序列 subsequence1 。从 word2 中选出某个 非空 子序列 subsequence2 。连接两个子序列 subsequence1 subsequence2 &…

python牛顿法计算平方根_常用的平方根算法详解与实现

本文从属于笔者的数据结构与算法系列文章。 SquareRoot 平方根计算一直是计算系统的常用算法&#xff0c;本文列举出几张简单易懂的平方根算法讲解与实现。其中Java版本的代码参考这里 Reference Babylonian:巴比伦算法/牛顿法 巴比伦算法可能算是最早的用于计算$sqrt{S}$的算法…

什么是spring_Spring 源码第三弹!EntityResolver 是个什么鬼?

上篇文章和小伙伴们说了 Spring 源码中 XML 文件的解析流程&#xff0c;本来可以继续往下走看加载核心类了&#xff0c;但是松哥还是希望能够慢一点&#xff0c;既然要学就学懂&#xff0c;在 XML 文件解析的过程中还涉及到一些其他的类和概念&#xff0c;因此我就先用几篇文章…

从RAID看垂直伸缩到水平伸缩的演化

learn from 从0开始学大数据&#xff08;极客时间&#xff09; 大规模数据存储问题&#xff1a; 容量问题&#xff0c;数据量超过磁盘容量读写速度&#xff0c;磁盘读写慢数据可靠性&#xff0c;磁盘寿命问题 RAID&#xff08;独立磁盘冗余阵列&#xff09; 是将多块普通磁盘…

linux安装g++编译器_Ubuntu Desktop下配置Rosetta安装教程

作者: 吴炜坤本文仅在虚拟机环境下测试&#xff0c;可能实际操作中会遇到不同的问题本文是新手向的安装教程&#xff0c;如果需要在CentOS上安装&#xff0c;可以参考本人其他安装教程由于许多新人朋友在学习Rosetta过程中&#xff0c;通常操作系统选择的都是带美丽漂亮界面便于…