简单TCP网络程序---多线程版本

在之前写的TCP网络程序中,我们用多进程的方式实现了一个服务器为多个用户提供服务。
简单TCP网络程序

简单UDP网络程序

链接://TODO

今天来实现一个服务器可以为多个用户提供服务。
接口之前已经介绍过了,今天在此处不再介绍,直接贴上代码

server

#include <stdio.h>
#include <error.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//简单TCP网络程序,可以实现简单聊天室

//******************服务器端********************
//TCP协议是建立连接的可靠传输协议
//1.绑定Ip 和 端口号
//2.被动打开,使可以被建立连接
//3.接受客户端的连接请求
//开始进行事件循环
//  4.接收来自客户端的请求
//  5.开始计算处理客户端发来的请求
//   a)因为这里是回显服务,没有过多的计算处理,只做以下处理
//   b)将客户端的请求显示在标准输出上
//   c)再将请求发回给客户端
//  6.对客户端发来的请求进行响应


//用多线程来实现一个服务器可以同时处理多个用户的请求和响应
//需要注意以下几个小点
//1.线程入口函数中的参数应该是堆上开辟的,不能是栈上或者全局的
//2.主线中应该处理对其他线程的资源的回收
    //这里不能使用线程等待函数,因为线程等待函数是阻塞式等待
    //这样就不能达到同时处理多个用户的请求和响应
    //应该采用线程分离,使内核自动回收线程资源
//3.应该处理文件描述符的关闭问题
    //因为同组线程之间是共用一块虚拟地址空间,共享文件描述符表
    //所以只要在一个线程任务处理完之后关闭文件描述符即可


typedef struct  Info
{
  sockaddr_in _peer;
  int fd;
}Info;


void * Entry(void * arg)
{

  Info * info =(Info *)arg;
  sockaddr_in peer =info->_peer;
  int new_socket=info->fd; 
  printf("Entry:%s:%d\n",inet_ntoa(info->_peer.sin_addr),info->fd);

  char buf[1024]={0};
//用来处理一次连接。需要循环的处理

  while(1)
  {
    //7.接收来自客户端的请求
    ssize_t read_size=read(new_socket,buf,sizeof(buf)-1);

    if(read_size<0)
    {
      continue;
    }
    if(read_size==0)
    {
      printf("client discount!\n");
      //用来标识用户已经关闭连接
      close(new_socket);
      return NULL;
    }

    buf[read_size]='\0';

    //8.将客户端的请求显示再标准输出上 
    printf("peer %s:%d  say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);

    //9.将请求发送给客户端

    write(new_socket,buf,read_size);
    printf("写会客户端\n");
  }

  return NULL;
}


void ProcessConnect(int new_socket,sockaddr_in peer)
{

  printf("参数:%s :%d\n",inet_ntoa(peer.sin_addr),new_socket);
  pthread_t pthread;

  //线程入口函数中参数应该是堆上开辟的

  Info  *info=(Info *)malloc(sizeof(Info));
  info->fd=new_socket;
  info->_peer=peer;
  printf("准备给线程传参:%s:%d\n",inet_ntoa(info->_peer.sin_addr),info->fd);

  pthread_create(&pthread,0,Entry,info);

  //回收线程的资源
  pthread_detach(pthread);
  return;

}


// ./service [IP] [Port]
int main(int argc,char * argv[])
{
  //1.检查命令行参数
  if(argc!=3)
  {
    printf("Usage: ./service [IP] [Port]\n");
    return 1;
  }

  //2.创建socket 
  int fd=socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return 1;
  }

  //3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中

  sockaddr_in addr;
  addr.sin_family=AF_INET;
  socklen_t addr_len=sizeof(addr);
  //将点分十进制的IP地址 转化 为32位数字
  addr.sin_addr.s_addr=inet_addr(argv[1]);
  //将端口号转位网络字节序
  addr.sin_port=htons(atoi(argv[2]));


  //4.服务器建立连接
  int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
  if(bind_ret<0)
  {
    perror("bind");
    return 1;
  }

  //5.服务器开始被动打开,处于可以被建立连接状态
  int listen_ret=listen(fd,5);
  if(listen_ret<0)
  {
    perror("listen");
    return 1;
  }

  //开始事件循环


  while(1)
  {
    //6.接受客户端的连接请求

    int new_socket=accept(fd,(sockaddr *)&addr,&addr_len);
    //这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
    //后续信息传输建立在这个socket
    if(new_socket<0)
    {
      perror("accept");
      continue;
    }
    //7.处理一次连接
    ProcessConnect(new_socket,addr);
  }
  close(fd);
  return 0;
}

client

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


//********************简单TCP网络程序
//********************客户端*************************
//
//TCP协议是建立连接的可靠传输
//1.与服务器建立连接
//2.向服务器发送请求
//3.接收来自服务器端响应



int main(int argc ,char *argv[])
{

  //检查命令行参数
  if(argc!=3)
  {
    printf("Usage : ./client [IP] [Port]\n");
    return 1;
  }
  //1.创建socket
  int fd=socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return 1;
  }

  //2.建立连接

  //将要建立连接的服务器的IP 和 Port 放在结构体中
  sockaddr_in service;
  service.sin_family=AF_INET;
  socklen_t service_len=sizeof(service);
  service.sin_addr.s_addr=inet_addr(argv[1]);
  service.sin_port=htons(atoi(argv[2]));

  int connect_ret=connect(fd,(sockaddr *)&service,service_len);
  if(connect_ret<0)
  {
    perror("connect");
    return 1;
  }


  char buf[1024]={0};

  while(1)
  {
    //3.向服务器发送请求
         //从标准输入开始读取数据
    ssize_t read_size=read(0,buf,sizeof(buf)-1);
    if(read_size<0)
    {
      perror("read");
      return 1;
    }
    if(read_size==0)
    {
      printf("done\n");
      return 0;
    }
    buf[read_size]='\0';
        //将数据发送给服务器

    write(fd,buf,read_size);

    //4.从服务器接收请求
        //构造结构体存放接收方的信息
        //这里的service会作为输出型参数,因为服务器端是任意Ip主机都保持连接状态,这里接收服务的参数,然后打印出来
    char buf_service[1024]={0};
    read(fd,buf_service,sizeof(buf_service)-1);

    if(read_size<0)
    {
      perror("recvfrom");
      return 1;
    }
    if(read_size==0)
    {
      printf("read done\n");
      return 0;
    }

    buf_service[read_size]='\0';
    //5.将从服务器接收的信息显示到标准输出

    printf("service[%s:%d] response :%s",inet_ntoa(service.sin_addr),ntohs(service.sin_port),buf_service);

  }

}


用多线程来实现一个服务器可以同时处理多个用户的请求和响应
需要注意以下几个小点

1. 线程入口函数中的参数应该是堆上开辟的,不能是栈上或者全局的
2. 主线中应该处理对其他线程的资源的回收
这里不能使用线程等待函数,因为线程等待函数是阻塞式等待
这样就不能达到同时处理多个用户的请求和响应
应该采用线程分离,使内核自动回收线程资源
3. 应该处理文件描述符的关闭问题
因为同组线程之间是共用一块虚拟地址空间,共享文件描述符表
所以只要在一个线程任务处理完之后关闭文件描述符即可

猜你喜欢

转载自blog.csdn.net/misszhoudandan/article/details/80831225
今日推荐