网络编程套接字-TCP

本文旨在实现TCP套接字通信的单进程版本,多进程版本,多线程版本

makefile

.PHONY:all
all:tcpServer tcpClient
tcpServer:tcpServer.c
    gcc -o $@ $^
tcpClient:tcpClient.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f tcpServer tcpClient
编写服务端程序

创建socket文件–>绑定端口号–>监听连接(底层)–>处理连接,即服务(来自监听的返回的连接)

tcpServer.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

typedef struct
{
      int sock;
      char buf[24];
      int port_t;
}net_info_t;
//准备函数
int startup(char *ip,int port )
{
      //创建socket文件
      int sock=socket(AF_INET,SOCK_STREAM,0);
      if(  sock<0)
      {
            printf("socket error\n");
            exit(2);
      }
      //绑定本地端口号和IP
      struct sockaddr_in local;
      local.sin_family=AF_INET;
      local.sin_addr.s_addr=inet_addr(ip);
      local.sin_port=htons(port);
      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
      {

            printf("bind error\n");
            exit(3);
      }
      //监听socket,监听成功即可以连接
      if(listen(sock,5))
      {

            printf("listen error\n");
            exit(4);
      }
      return sock;


}
//服务函数
void service(int new_sock,char *ip,int port)
{
      char buf[124];
      while(1)
      {
            buf[0]=0;
            //读取网络中的数据
            ssize_t s=read(new_sock,buf,sizeof(  buf)-1);
            if(s>0)
            {
                  buf[s]=0;
                  printf(" [%s:%d] say#      %s\n",ip,port,buf);
                  //往网络中写数据
                  write(new_sock,buf,strlen(buf));
            }
            else if(s==0)
            {
                 printf("client [%s:%d]quit!\n",ip,port);
                 break;
            }
            else
            {
                  printf("read error\n");
                  break;
            }

      }
}


//主函数
int main(int argc,char * argv[])
{
      if(argc!=3)
      {
            printf("Usage:%s [iP][port]\n",argv[0]);
            return 1;
      }
      //获取新连接(从底层获取新连接)
      int listen_sock=startup(argv[1],atoi(argv[2]));
      struct sockaddr_in peer;
      //定义一个缓冲区,用于存放ip地址
      char ipBuf[24];
      while(1)
      {
            ipBuf[0]=0;
            socklen_t len=sizeof(peer);
           //获得新连接(从监听套接字中拿连接),提供数据io的服务
            int new_sock=accept(listen_sock,(struct sockaddr*)&peer,&len); 
            if(new_sock<0)
            {

            }
            //将网络中的地址转换成字符串形式
            inet_ntop(AF_INET,(  const void*)&peer.sin_addr,ipBuf,sizeof(ipBuf));
            int port=ntohs(peer.sin_port);

            printf(" get a newconect,[%s][ %d]\n",ipBuf,port);


             ///////////////以下部分为可替换部分//////////////




            //1.单进程版本
            service(new_sock,ipBuf,port);
            //关闭socket文件描述符
            close(new_sock);
            //////////以上为可替换部分///////////////////////////////////
      }



      return 0;
}

这里写图片描述
查看主机中网络服务器如下
这里写图片描述

编写客户端程序

创建socket文件–>创建连接(connect)

tcpClient.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char*argv[ ])
{
      int sock=socket(AF_INET,SOCK_STREAM,0);
      if(  sock<0)
      {
            printf("socket error\n");
            return 1;

      }
      //填充服务器信息
      struct sockaddr_in server;
      server.sin_family=AF_INET;
      server.sin_addr.s_addr=inet_addr(argv[1]);
      server.sin_port=htons(atoi(argv[2]));

      //连接服务器
      if(connect(sock,( struct sockaddr*)&server,sizeof(server))<0)
      {

            printf("connect error\n");
            return 1;
      }
      char buf[124];
      while(1)
      {
            printf("please Enter#");
            fflush(stdout);
            //从标准输入读取数据,写进缓冲区
            ssize_t s=read(0,buf,sizeof(  buf)-1);
            if(s>0)
            {
                buf[s-1]=0;
                //判断客户端是否退出
                if( strcmp("quit",buf)==0)
                {
                      printf("client quit!\n");
                      break;
                }
                //将缓冲区数据写进sock文件
                write(sock,buf,strlen(buf));
                //将sock文件写进缓冲区
                s=read(  sock,buf,sizeof(  buf)-1);
                buf[s]=0;
                //输出缓冲区内容
                printf("Server# %s\n",buf);

            }
      }
      close(sock);
      return 0;

}

在本文中,我采用回环地址测试即ip地址:127.0.0.1,端口随意设置
这里写图片描述
测试结果:将服务器运行起来,客户端连接服务端,IP地址和端口 号一致。
即可以看到下面图中的运行结果,模拟网络中两个进程通信

这里写图片描述
这里写图片描述
缺点:这种单进程服务器,基本就是一种废的服务器。因为该服务器只能为一个客户端服务。只要一个客户连接上该服务器,其他用户即使在同一个局域网上,也会连接不上服务端,只有当当前的客户端退出,其客户端才能连接上他。并且谁能连上也不一定。因为存在多个客户端竞争一个服务端的问题。

为了解决上述问题,我们现在将单进程版本服务器改成多进程服务器。

多进程版本(将上述服务器程序替换部分的源程序替换掉成下列程序即可)

我们采用让子进程进行数据IO服务,父进程监听链接的思路实现多进程版本服务器
但是由于父子进程之间有等待与被等待的关系。如果父进程不等待子进程,子进程就会变成僵尸进程,进而导致内存泄露的严重问题。如果父进程等待子进程,不管是阻塞式等待。还是非阻塞式等待,效果都不是很好。

虽然此时我们会想到用信号SIGCHILD信号去处理子进程。但是实现验证,效果也不是很理想。

所以我们另辟蹊径,考虑到父子进程有等待关系,我们为了避免这个问题。采用父进程创建子进程,然后子进程再创建子进程(我们叫孙子进程),这时让子进程退出。让孙子进程执行数据IO服务。就解决了等待问题。因为爷孙进程之间不存在等待关系,子进程退出后,孙子进程退出后由1号进程领养处理。也解决了僵尸进程问题

(运行结果和多线程结果一样,只在多线程版本展示运行结果)

程序如下:

        //创建子进程
        pid_t id=fork(  );
        //子进程
        if(id==0)
        {
              //关闭监听文件描述符
              close(listen_sock);
              //创建子进程创建孙子进程,让孙子进程提供服务
              if((fork)>0)
              {
                    exit(0);
              }
              service(new_sock,ipBuf,port);
              //关闭new_socket文件描述符
             close(new_sock);
              exit(0);
        }
        //父进程
        else if(id>0)
        {
             close(new_sock);
              //等待子进程
              waitpid(id,NULL,0);
        }
        //创建失败
        else
        {
              //重新获取listen套接字
              printf("fork error!\n");
              continue;
        }





多进程版本

         net_info_t* point=(net_info_t*)malloc(sizeof(net_info_t));
         if(point==NULL)
         {
               printf("molloc error\n");
               close(new_sock);
               continue;
         }
         /封装的结构体初始化
         point->sock=new_sock;
         strcpy(point->buf,ipBuf);
         point->port_t=port;
         pthread_t tid;
         //创建新线程
         pthread_create(&tid,NULL,thread_service,(void *)point);
         //分离线程
         pthread_detach(tid);
```
//多线程辅助函数
void* thread_service(void *arg)
{
      net_info_t* p=(net_info_t*)arg;
      service(p->sock,p->buf,p->port_t);
      close(p->sock);
      free(p);
}


客户端结果图
这里写图片描述
服务端结果图
这里写图片描述

对象性能问题接受客户数量CPU压力稳定性是否共享文件描述符
单进程进程数量很少,最多两个,所以性能好。一个压力非常小稳定性很好
多进程创建子进程,耗时长每个进程占用资源多,导致连接客户端数量有限进程增多,CPU压力大,调度器十分忙碌进程具有独立性,所以稳定行好
多线程创建子进程,耗时较短每个进程占用资源多,导致连接客户端数量比进程多很多轻量级线程,占用资源少,CPU处理方便线程共享进程虚拟地址空间,导致稳定性差

进行网络连接,进行网络通信命令如下

(在编写好服务端,客户端程序后执行,其中服务器主机和客户端主机连接同一个局域网)

1.将网络连接设置成桥接模式
这里写图片描述
这里写图片描述

2.关闭防火墙


 sudo service iptables stop

2 .拷贝客户端程序给目标主机

ping  目标网络IP地址

scp tcpclient.c 目标网络IP地址(同上)  路径

比如

ping 192.168.3.59

scp tvpClient.c 192.168.3.59 ../home

客户主机执行tcpClient.c 程序即可完成多用户通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值