服务器编程1-socket

 

所有代码环境没有说明都是VMware中运行的Ubuntu18里。

 

:写这么多东西,我觉得是对知识的梳理吧,一个是防忘,再者,是构建一个完整体系。

:废话比较多,我写的时候就像是在给一个只有一点编程基础的人对话,似乎要给他讲明白一样

:解释将大量以注释的形式表述

 下面就是一个简单的,多线程回写服务器

 1 #include <iostream>
 2 #include <unistd.h>
 3 #include <arpa/inet.h>
 4 #include <string.h>
 5 #include <fcntl.h>
 6 #include <pthread.h>
 7 
 8 
 9 #define SRV_PORT 8102            //服务器监听端口
10 #define MAX_SYN_WAITING 5          
11 using namespace std;
12 
13 void* thread_task(void *fd){    
14     char buffer[128]={0};
15 
16     int cli=*(int *)fd;
17 
18     while(read(cli,buffer,128)>0){
19 
20         write(cli,buffer,strlen(buffer));
21       
22 
23 
24     }
25 
26     return NULL;
27 
28 }
29 
30 
31 
32 int main()
33 {
34     int srv_socket=socket(AF_INET,SOCK_STREAM,0);
35 
36     
37     sockaddr_in srv_addr;
38 
39     srv_addr.sin_family=AF_INET;
40     srv_addr.sin_port=htons(SRV_PORT);
41     srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
42 
43     bind(srv_socket,(sockaddr*)&srv_addr,sizeof(sockaddr));
44     listen(srv_socket,MAX_SYN_WAITING);
45 
46     int client_fd;
47     pthread_t pid_t;//unsigned int
48     while(1){
49         client_fd=accept(srv_socket,NULL,NULL);
50 
51         pthread_create(&pid_t,NULL,thread_task,(void *)&client_fd);//成功的pid,线程属性结构体的地址,启动函数(返回void*,参数void*),void*参数
52         pthread_detach(pid_t);
53         
54 
55     }
56 
57 
58     return 0;
59 }

 

setsockopt可加可不加,可用于取消2MSL的等待时间

 

先说思路,TCP服务器,34-44行都是常规操作,先建立一个socket(我的理解就是一块读写缓冲区),自己填一个sockaddr地址结构体,然后将sockaddr绑定到前面那个socket()返回的srv_socket

  在这一步骤里,socket()应该是你,向操作系统注册一个socket,socket在内核空间,不能直接读写,暂无地址,操作系统返回你管理那个socket的句柄(...windows程序员)----描述符,然后你自己声明了一套地址,bind到那个socket上,操作系统由此知道,这个端口分配给对应的socket,细节部分要注意到,x86-64或者说AMD64的cpu是小端序,要转到网络字节序,用到htons,(两字节),htonl(4字节),类比于“hello world”变成“world hello”不是“olleh这样!!!”,每个字节不变,只是字节之间顺序改变。参数部分简单说说吧,AF_INET代表协议ipv4,SOCK_STREAM,流式传输协议,加上0--》TCP协议,还有SOCK_DGRAM,0 UDP协议。INADDR_ANY,本机任意ip的对应端口,服务器一般都这样吧。值为0,0,0,0.

 

   listen代表变为被动监听的socket,也就是说,让系统给你留心听着,listen是否有人connect这个socket,如果有,你可以异步accept那个和你建立链接的人。第二个参数有意思,表示最大同时链接数,

这个可以联系到TCP的三次握手,当对方发来SYN后,listen的端口会反馈SYN/ACK,这个时候对面再来一个ACK,就建立完成,但是对面可能有恶意,故意发个SYN就跑,这样浪费你的空间时间,去等待ACK,

这个时候,就有一个在建立中的TCP链接。listen第二个参数表示最多有多少个建立中的链接,超出的直接丢掉。

   48行开始处理新的链接请求.逻辑描述为,从listen的socket里阻塞式accept得到用于和客户端通信的socketfd,也可以设置为非阻塞,不过目前先不必要.然后返回的cfd用于和客户端通信.然后创建新的线程,

pthread_creat非linux标准库编译要加链接选项-lpthread关于线程属性,比如分离属性,优先级等等.然后52行将线程设为分离态,目的是线程自己结束,回收资源.

  accept得到的究竟是什么,这个问题我差不多查了好多搞的差不多清楚了,有个问题不知道看客是否又想过,accept得到cfd端口和listen的lfd相同,那么是如何区分的?

  这个人写的文章解决了这一个困惑,原文“对影成三人” 博客,请务必保留此出处http://ticktick.blog.51cto.com/823160/779866,太长,我反正搞明白了,愿意搞清楚的可以自己看看吧.

 

  上述代码有个问题,我们假设客户端发来的都是小于128字节的可读字符串,假如读入的内容将128个字节全填充非0,strlen可能会越界,然后SegmentationFault,段错误且没有任何捕获异常的函数应该会导致整个进程崩溃.

  

  这个简单的模型有许多性能上的问题,假设有上千个客户端同时链接,那么在服务器就要有同时上千个线程对应处理业务.但是并不是忙等,线程阻塞在read函数,内核负责唤醒线程继续.可以通过任务管理看到cpu占用率得知.不过即便如此,对空间的消耗也是巨大的,每一个线程对象都要占一定的空间.还有就是频繁的线程创建销毁是相当耗时的操作.

 

  很容易就会想出线程池的概念,线程并不会等于客户数量,线程空闲则阻塞起来,当有任务到达,则从任务队列中取出一个任务.-----------------  与此相结伴的一点,任务是什么?每个客户端吗?不,对于网络编程中,像我这样的经常打开一个网页不关闭的人很多,如果就因此占用一个线程也是不可接受的.所以对于每个客户端,要划分为读写任务两部分.客户端什么都不干的时候,只保存状态就好了.

  io转接,这个思想不仅仅用于网络编程,在操作系统的设计中也是无处不在.

  说到这里不妨多思考一些,计算机内的设备其工作速率参差不齐,高速如CPU,低速如硬盘,打印机等等.如果一个进程遇到一个打印任务,它占用CPU时间片,等待打印机打印完毕,那么在不调度的系统中,高速设备CPU被打印机拖慢速度.这是不可接受的,于是,当与外围低速IO设备交换数据,该进程被阻塞,于是它立刻不具有占用CPU时间片的资格,被抢占CPU,进程进入阻塞队列,没有被调度的资格.何时恢复执行呢?等到外围设备完成操作,通过硬件中断告诉CPU,我的任务完成了.操作系统负责对这个中断的解释,操作系统把刚刚因为这个外围设备而阻塞的进程改成就绪态,然后正常分配到CPU,继续执行.对于整个计算机来说,操作系统并没有浪费CPU时间,但是对于那个顺序执行的进程而言,它真的等了至少外围io的时间,而且,往往IO操作是要等待的,比如socket中read,如果对端迟迟不write,函数一直不会返回..这就是同步阻塞IO,就像左脚右脚迈步,逻辑简单.

  

  接下来是同步非阻塞IO,区别在于IO的操作时,立刻返回(不论外围设备是否准备好)然后根据返回值判断是否完成,比如read得到<0,errno设为EAGAIN,代表暂无数据,过一会再读就好,EINTR代表阻塞时被中断.

可参考 http://www.cnblogs.com/longingforlife/p/3289976.html

  这使得你的程序可以定时检查去尝试read,或write,若未就绪,可以先做的别的事,取决于你的代码.但这并不是真正的异步.只是不会阻塞到原地.

  异步IO,理解起来就是,我对这个IO操作要做的事是XXX,然后你告诉操作系统,让操作系统那个IO就绪的时候,直接调用XXX,不需要要告诉我它就绪了,只要当你执行完XXX告诉我就行.这个XXX是一个回调函数.

异步IO的实现对操作系统有略高的要求,当然这是非阻塞的.

  

   非阻塞实现了一个重要的操作.比如,我有1000个IO要处理,如果阻塞到第一个IO上,那么就算后面的IO就绪了也得不到执行,这非常影响.而阻塞可以循环,哪个好了就做哪个.如果嫌一直循环容易忙等可以加个sleep.

这就是IO转接的开始了,从一个IO队列里,每次选出一个就绪的去执行任务.于是select模型诞生了.

  

 

转载于:https://www.cnblogs.com/slowman/p/9917358.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值