网络套接字编程(二)(TCP套接字的编写+多进程版本+多线程版本+线程池版本)

gitee仓库:https://gitee.com/WangZihao64/linux

TCP相关的socket API

listen——将套接字设置为监听状态,然后去监听socket的到来

#include <sys/socket.h> 
int listen(int s, int backlog);

参数:

  • s:要设置的套接字(称为监听套接字,通过socket创建)
  • backlog:连接队列的长度(不建议设置太长,后面的文章会详细介绍这个参数)

返回值: 成功返回0,失败返回-1

accept——接受请求,获取建立好的连接

#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

参数:

  • s:监听套接字
  • addr:输出型参数,获取远端连接的相关信息
  • addrlen:输入输出型参数,获取addr的大小长度

返回值: 成功返回一个连接套接字,用来标识远端建立好连接的套接字,失败返回-1

connect——发起请求,请求与服务端建立连接(一般用于客户端向服务端发起请求)

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd:套接字,发起连接请求的套接字
  • addr:描述自身的相关信息,用来标识自身,需要自己填充,让对端知道是请求方的信息,以便进行响应
  • addrlen:描述addr的大小

返回值: 成功返回0,失败返回-1

不知道大家是否对accept会有疑惑,已经通过socket创建好了一个套接字,accept又返回了一个套接字,这两个套接字有什么区别吗?UDP只又一个套接字就可以进行通信了,而TCP还需要这么多个,这是为什么?

答案是肯定有的,socket创建的套接字是用来服务端本身进行绑定的。因为UDP是面向数据报,无连接的,所以创建好一个套接字之后直接等待数据到来即可,而TCP是面向连接,需要等待连接的到来,并获取连接,普通的一个套接字是不能够进行连接的监听,这时就需要用的listen来对创建好的套接字进行设置,将其设置为监听状态,这样这个套接字就可以不断监听连接状态,如果连接到来了,就需要通过accept获取连接,获取连接后返回一个值,也是套接字,这个套接字是用来描述每一个建立好的连接,方便维护连接和给对端进行响应,后期都是通过该套接字对客户端进行通信,也就是对客户端进行服务。
所以说,开始创建的套接字是与自身强相关的,用来描述自身,并且需要进行监听,所以我们也会称这个套接字叫做监听套接字,获取到的每一个连接都用一个套接字对其进行唯一性标识,方便维护与服务。
一个通俗的类比,监听套接字好比是一家饭馆拉客的,不断地去店外拉客进店,拉客进店后顾客需要享受服务,这时就是服务员对其进行各种服务,服务员就好比是accept返回的套接字,此时拉客的不需要关心服务员是如何服务顾客的,只需要继续去店外拉客进入店内就餐即可。

基于TCP协议的套接字编程

服务端

TCP服务端的编写分多个版本:多进程、多线程、线程池三个版本,有这么多个版本主要是因为TCP要去服务多个不同的连接,所以单进程目前来看是不现实的。

整体框架

封装一个类,来描述tcp服务端,成员变量包含端口号和监听套接字两个即可,ip像udp服务端一样,绑定INADDR_ANY,构造函数根据传参初始化port,析构的时候关闭监听套接字即可

class TcpServer
    {
        public:
        TcpServer(uint16_t port)
        :_listensock(-1)
        ,_port(port)
        {}
        ~TcpServer()
        {
          if (_listen_sock >= 0) close(_listen_sock);
        }
        private:
        int _listensock;
        uint16_t _port;
    };
}

服务端的初始化

创建套接字

和UDP不同的是,TCP是面向连接的,所以第二个参数和TCP是不同的,填的是SOCK_STREAM

void Init()
{
	// 创建套接字
	_listensock=socket(AF_INET,SOCK_STREAM,0);
  if(_listensock<0)
  {
     LogMessage(FATAL,"socket create err");
     exit(SOCKET_ERR);
  }
  LogMessage(NORMAL,"socket create success");
}

绑定端口号(和udp一样这里不作介绍)

将套接字设置为监听状态

这里就需要用的listen这个接口,让套接字处于监听状态,然后可以去监听连接的到来


void Init()
{
    if(listen(_listensock,5)==-1)
    {
        LogMessage(FATAL,"listen err");
        exit(LISTEN_ERR);
    }
    LogMessage(NORMAL,"listen success");
}

循环获取连接

监听套接字通过accept获取连接,一次获取连接失败不要直接将服务端关闭,而是重新去获取连接就好,因为获取一个连接失败而直接关闭服务端,带来的损失是很大的,所以只需要重新获取连接即可,返回的用于通信套接字记录下来,进行通信,然后可以用多种方式为各种连接连接提供服务

void start()
{
    for(;;) {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sock = accept(_listensock, (struct sockaddr *) &peer, &len);
        if (sock < 0) {
            LogMessage(ERROR, "accept err");
            continue;
        }
        LogMessage(NORMAL, "accept success %d ", sock);
    }
}

客户端

很多地方和服务器差不多,不再介绍,这里只介绍重点

客户端启动

发起连接请求

使用connect函数,想服务端发起连接请求,注意,调用这个函数之前,需要先填充好服务端的信息,有协议家族、端口号和IP,请求连接失败直接退出进程,重新启动进程即可,连接成功之后就可以像服务端发起各自的服务请求(后面介绍),代码如下:

void start()
{
    struct sockaddr_in send;
    bzero(&send, sizeof(send));
    send.sin_family = AF_INET;
    send.sin_port = htons(_port);
    send.sin_addr.s_addr = inet_addr(_ip.c_str());
    if (connect(_sockfd, (struct sockaddr *)&send, sizeof(send)) == -1)
    {
        LogMessage(FATAL, "connect err");
        exit(CONNECT_ERR);
    }
    else
    {

    }
}

发起服务请求

请求很简单,只需要让用户输入字符串请求,然后将请求通过write(send也可以)发送过去,然后创建一个缓冲区,通过read(recv也可以)读取服务端的响应,这里需要着重介绍一下read的返回值

read的返回值:

  1. 大于0:实际读取的字节数
  2. 等于0:读到了文件末尾,说明对端关闭,用在服务端就是客户端关闭,用在客户端就是服务端关闭了,客户端可以直接退出
  3. 小于0:说明读取失败
else
{
     while (1)
     {
          // 发送消息
          cout << "Enter# ";
          string message;
          getline(cin,message);
          write(_sockfd, message.c_str(), message.size());
          char buffer[1024];
          int n=read(_sockfd,buffer,sizeof(buffer)-1);
          if(n>0)
          {
              buffer[n]=0;
              cout << "Server回显# " << buffer << endl;
          }
          else
          {
              break;
          }
      }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值