【网络编程】TCP服务的特点。多线程处理并发。(流式服务的特点)

  • ip地址:ipv4 ipv6唯一标识一台主机《高性能服务器编程》第三章和了解第2章ip报头
  • mac地址:48位
  • 端口:应用程序的代号。
  1. 虽然pid也能唯一标识一个进程,但是pid会变化,
  2. 无法并发的处理两个客户端。
  3. 两个人同时给一个人打电话,肯定有一个打不通(使用多线程或多进程就可以)
  4. //如果为空,accept就会阻塞住
    socket 门迎
    c = accept() 服务员

用多线程来处理并发

。主线程负责处理连接
子线程 接收数据
ser.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<pthread.h>
void *work_fun(void *arg)
{
        int c = (int) arg;
        while(1)
        {
        char buff[128] = {0};
        int n = recv(c,buff,127,0);//recv 如果接受不到数据,会阻塞
        if(n <= 0)
        {
                break;
        }
        printf("buff(%d) = %s\n",c,buff);

        send(c,"ok",2,0);
        }
        printf("client close\n");
        close(c);
}
int main()
{

     int sockfd = socket(AF_INET,SOCK_STREAM,0);
     if(sockfd == -1)
     {
          exit(1);
      }

     //专用套接子地址结构
     struct sockaddr_in saddr,caddr;  
     memset(&saddr,0,sizeof(saddr));
     saddr.sin_family = AF_INET;
     saddr.sin_port = htons(6000);
     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
     
     
     int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
     if(res == -1) 
     {      
             close(sockfd);
             exit(1);
     }


     res = listen(sockfd,5);
     if(res == -1)
     {
             close(sockfd);
             exit(1);
     }

     while(1)
     {
     int len = sizeof(caddr);
     int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//只有被处理的已完成的连接才能接受数据
     if(c < 0) //连接套接字
     {
           continue;
      }
     printf("accept :%d\n",c);

     pthread_t id;
     pthread_create(&id,NULL,work_fun,(void*)c);
     }
}

cli.c

int main() 
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1 )
    {
           exit(1);
     }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;    //家庭族  ipv4
    saddr.sin_port = htons(6000); // 1024  4096保留端口
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //ip地址

    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res == -1)
    {
            printf("connect falied\n");
            close(sockfd);
           exit(1);
     }


    while(1)
    {
    char buff[128] = {0};
    printf("input\n");
    fgets(buff,128,stdin);
    if(strncmp(buff,"end",3)== 0)
    {
            break;
    }
    send(sockfd,buff,strlen(buff)-1,0);   
    memset(buff,0,128);                      //每次发送完数据,将缓冲区清空
    recv(sockfd,buff,127,0);            
    printf("recv = %s\n",buff);
    }
    close(sockfd); 
    exit(0);
}

请添加图片描述

1、TCP :面向连接可靠的流式服务
比如你发快递,第一次给快递员,快递员拿走了,(已经在规定时间发货)(还没有发货,放在快递站仓库),你又要去再发一件,你的两件快递发出去的可能性就是,第一个已经发了,第二个后发和两个同时发出。当收件人的情况也是,力气大一点可以两个一块拿走,力气小就得分两次,或者件太大只能一次拿一个,件小可以一次收走。
同理对应recvsend
send 是连续多次的将数据写到发送缓冲区中

recv和send 接收数据和发送数据的方式不是对应的(因为缓冲区)
请添加图片描述
如下图结果:
请添加图片描述

《高性能服务器编程》Tcp服务的特点第三章

  int n = recv(c,buff,1,0);//recv 如果接受不到数据,会阻塞
  ok还在发送缓冲区,后面的数据接受时

netstat -natp
请添加图片描述

打个比方比喻TCP,有个蓄水池,你可以里面倒水,蓄水池上有个龙头,你可以通过龙头将水池里的水放出来,然后用各种各样的容器装(杯子、矿泉水瓶、锅碗瓢盆)接水。

上面的例子中,往水池里倒几次水和接几次水是没有必然联系的,也就是说你可以只倒一次水,然后分10次接完。另外,水池里的水接多少就会少多少;往里面倒多少水,就会增加多少水,但是不能超过水池的容量,多出的水会溢出。

结合TCP的概念,水池就好比接收缓存,倒水就相当于发送数据,接水就相当于读取数据。好比你通过TCP连接给另一端发送数据,你只调用了一次write,发送了100个字节,但是对方可以分10次收完,每次10个字节;你也可以调用10次write,每次10个字节,但是对方可以一次就收完。(假设数据都能到达)但是,你发送的数据量不能大于对方的 (流量控制),如果你硬是要发送过量数据,则对方的缓存满了就会把多出的数据丢弃。

这种情况是设置非阻塞I/O模型,会把内存耗尽,因为socket是存在内核中的。

2、UDP :无连接的不可靠的数据包服务 (视频,图片,大文件)

UDP和TCP不同,发送端调用了几次write,接收端必须用相同次数的read读完。UPD是基于报文的,在接收的时候,每次最多只能读取一个报文,报文和报文是不会合并的,如果缓冲区小于报文长度,则多出的部分会被丢弃。也就说,如果不指定MSG_PEEK标志,每次读取操作将消耗一个报文。

使用进程实现并发(作业)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值