网络编程2

本文详细介绍了TCP服务端的初始化、绑定、监听、接受连接过程,以及如何通过多进程优化以支持多个客户端连接。还涵盖了客户端的套接字创建、连接、数据传输等内容。

一.TCP代码服务端的编写

要设计TCP程序,首先调用逻辑肯定是不变的:

我们还是用这样的开发模式,client直接调用系统接口,server采用自己的封装

服务器serverinit 

创建套接字

既然要初始化,那势必要创建套接字

还是socket接口 :

int socket(int domain, int type, int protocol)
返回值:成功返回一个文件描述符,失败返回-1
domain:AF_INET,表示的是通信类型AF_INET代表网络通信AF_UNIX是本地通信
type:SOCK_STREAM面向字节流式的通信,注意前面我们创建udp的套接字的时候用的SOCK_DGRAM面向数据报
protocol:0,只要是IP/TCP协议下的网络通信,前面两个参数传了,第三个参数直接写0就行(它代表协议)

(我们还是用log.hpp来对程序信息进行显示,log.hpp详见博客<网络编程1>)

 bind绑定

还是老问题,我们需要将ip和port绑定到sockaddr_in中

int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
返回值:成功返回0,失败返回-1
sockfd:刚刚创建的套接字_sock
addr:我们需要定义一个addr,其中放上服务器的port和ip,然后将它传到这个参数
addrlen:就是sizeof(addr),可以看做是个固定用法

*区别于udp*等待连接

 因为TCP需要建立连接才能通信,所以它要先处于一个等待别人连接它的状态.

也就是将套接字状态设置为监听状态 

int listen(int sockfd, int backlog)
返回值:成功返回0,失败返回-1
sockfd:套接字
backlog:这里我们先暂且设置为20,这个值不能太大也不能太小(全连接队列长度,后面在详细解释)

 

 完成

 listen的第二个参数目前还无法理解,先暂且不谈

服务器serverstart

首先肯定还是要注意服务器是要一直运行的。

在之前的udp编程中,我们使用netstat -anup命令来查看本机的udp程序,类似的我们可以用netstat -antp来查看tcp程序. 

我们用这个命令是可以查看到当前服务器是出于listen状态的.

*获取链接*

因为tcp是面向连接的,所以是要先获取链接才能获得数据的.

获取链接
int accept(int sockfd ,struct sockaddr* addr,socklen_t* addrlen)

返回值:成功返回一个文件描述符(套接字sock),失败返回-1
sockfd:就是刚刚创建并绑定好的套接字
addr:输出型参数,返回获取的ip地址
addrlen:固定写为sizeof(addr),是输入输出型参数
这后面的两个参数和recvfrom的后两个参数是很类似的.

 对于accept的返回值,它同样是一个套接字,那么这个套接字是干嘛的呢?

答:accept的套接字就是获取连接之后提供服务的,而原来我们创建并绑定并listen的套接字,它的职责就是专门用来获取新的连接的:

我们将上面创建的套接字改名称为"listensock",将accept到的套接字该名称为"servicesock".

 获取链接成功之后就要进行服务了:

我们这里用函数service来进行服务的动作

service  

和udp一样,我们还是实现一个echo服务器:也就是把收到的数据再发回客户端

在这里我们要注意的就是read的返回值为0的情况就是:对端的数据已经全部读完了,此时对端就会自动关闭链接,因此就没得东西读了.并且此时我们的服务端也要关闭链接.

 

这样一来TCP实现的服务器基本就完成了:

 

快速对服务器进行测试 

我们不使用客户端,可以用telnet来直接与服务器建立连接:

  

 如果没有telnet的命令,就需要安装一下:sudo yum -y install telnet

 此时在telnet下,按 Ctrl键 + "}]"键,就会有如下显示:

意为,向所连接的服务器发送数据,此时我们输入数据就会被服务器以字符串的形式获取到:

想要退出telnet时,就只需再输入一次  Ctrl键 + "}]"键 进入telnet界面,再输入quit就可以退出.

这样就完成了基本的服务器的测试.

缺陷----无法实现多个客户端连接服务器

但是,此时我们的服务器只能链接一个服务器,当我们用多个telnet连接时,我们会发现我们所发数据是无法同时被服务器接受的.

 

因为这里的我们实现的服务器是个单进程的,它在同一时间只能连接上一个客户端的,处理完了当前的客户端才能去处理下一个客户端.

二.TCP服务器代码的优化

正如缺陷中所述,当前我们实现的服务器是个单进程循环服务器,它无法同时连接上多个客户端,因此我们需要优化一下它 ,这里的思路也很明确------实现一个多进程版本的服务器,让子进程来提供服务,父进程来建立连接

 我们拟出如下的框架,但是又有这样的问题:

如果子进程完成了服务任务,我们需要让它自动退出,但是子进程退出时会进入僵尸状态,是需要被回收的,这就又导致了父进程要等待它,而父进程等待是阻塞式等待,在等待子进程退出时又会进入阻塞状态,这样的连锁反应之下,我们这个服务器在实现多个客户端连接时,其性能就没有什么优越性了(父进程一直处于阻塞状态一个一个等)

因此我们是绝对不能用waitpid这样的方法的.

解决方案:

1.解决僵尸问题

我们可以使用信号忽略的方式来忽略掉子进程的退出信号,这样就能让子进程自动释放资源 

2.父进程建立连接,子进程处理服务

文件描述符也是有限的,要让父进程关闭掉提供服务的文件描述符(套接字),子进程关闭提供链接的文件描述符来节省文件描述符资源

理论上来说子进程可以不关闭 listensock ,但是父进程必须要把 servicesock 关闭,不然的话会导致文件描述符资源越来越少,最后没有文件描述符可以用.(文件描述符泄露) 

 

 把父进程该做的事给加入,让它处理服务任务:

这样一来就完成了多进程版本的TCP服务器的编写. 

三.TCP代码客户端的编写

 创建套接字,不需要bind

发起连接 

int connect(int sockfd, const struct sockaddr* addr,socklen_t addrlen)
sockfd:当前套接字,表示谁发起的连接
addr:连谁
addrlen:sizeof addr 固定写法
返回值:成功返回0,失败返回-1

 

发送数据

连接成功之后我们可以发送数据给服务器 :

我们之前使用的sendto和write,其中sendto是给UDP用的,而write和send都可以用于TCP,这里我们试试用send:

ssize_t send(int sockfd, const void *buf,size_t len,int flags)
//它的前三个参数和write是一模一样的
sockfd:谁要发送数据
buf:发什么
len:要发多少(期望发多少数据)
flags:0
返回值:成功返回0,失败返回-1

这里我们提前说一下,读数据也可以用recv:

ssize_t recv(int sockfd,void* buf,size_t len,int flags)
//它的前三个参数和read一模一样
sockfd:谁要读数据
buf:读到哪里
len:要读多少(期望读多少数据)
flags:0
返回值:成功返回大于0(读到多少个数据),失败返回-1

 

接收数据

 

初步完成

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值