Linux--tcp编程(循环读数据、多客户端)

目录

一、简介:

二、基础准备

1.IP地址转换函数:

2. 套接字地址结构

1.通用socket地址结构:

2.专用socket地址结构:

三、编程流程 

四、服务器端编程流程 

//头文件:

代码

 //可以查看端口号

五、客户端编程:

六、连接后运行结果

七、循环写入数据

1.客户端代码:

2.服务器端代码:

3.执行结果:

八、如果用两个终端同时运行./client会怎样?

1.结果如何:

2.数据还能发送吗?

3.中间数据去哪了?

4.总结:

九、利用多线程,同时运行多客户端

1.代码:

2.运行结果:


一、简介:

在linux操作系统下完成TCP(socket、bind、listen、accept、recv、close、send、connect)网络编程,TCP服务器利用socket通过网络收发数据的能力,使用bind指定ip+端口、listen创建监听队列、accept接受链接、recv接受发送的数据、send回复数据以及最后利用close关闭链接;TCP客户端首先创建socket,利用connect向服务器发起链接,send发送数据,recv接受服务器反馈回来的数据,最后close关闭链接。能够在服务端程序和客户端程序互相传递信息,并能够使用fork和thread完成多进程和多线程编程,使多个客户端能够同时向服务器端发送数据。

二、基础准备

1.IP地址转换函数:

 inet_addr :把字符串转换为整型 

2. 套接字地址结构

通过socket可以在网络上进行数据的接收发送,(网络程序一定有套接字);

套接字包括IP地址和端口号

1.通用socket地址结构:

struct sockaddr
{
    sa_family_t sa_family;
    char sa_data[14];
};

2.专用socket地址结构:

//IPv4

struct sockaddr_in
{
    sa_family_t sin_family;//地址族

    u_int16 sin_port;//端口号

    struct om_addr sin_addr;//ip地址
};

//ipv6

struct in6_addr
{
    unsigned char sa_addr[16];//IPV6地址,要用网络字节序表示
};

 

三、编程流程 

四、服务器端编程流程 

//头文件:

// man inet_addr

//服务器端两个套接字,一个是监听套接字,一个是连接套接字

//l链接n个客户端,就会产生n+1个描述符(多一个监听套接字)

//sockfd:监听套接字

//c:连接套接字

代码

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

int main()
{
    //1.创建套接字
    int sockfd=socket(AF_INET,SOCK_STREAM,0);//AF_INET是IPV4协议族
    if (sockfd==-1)
    {
        printf("创建套接字失败\n");
        exit(1);
    }//创建套接字失败了

    //2.设置IP端口
    struct sockaddr_in saddr,caddr;//服务器端、客户端
    memset(&saddr,0,sizeof(saddr));//把saddr全部置为0,即就是置空
    saddr.sin_family=AF_INET;//成员设置地址族
    saddr.sin_port=htons(6000);//主机转网络段整形,端口号,使用5000以上的端口,
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//回环地址,自己给自己发

    int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定套接字,专用强转成通用套接字地址 //给套接字指定,使用saddr中给的IP地址和端口
    if(res==-1)//失败了,端口号(6000)被占用了;IP地址写错
    {
        printf("bind err\n");
        close(sockfd);//关闭套接字
        exit(1);//出错退出
    }

    //3.创建监听队列
    res=listen(sockfd,5);//5是循环复用的,不代表只能接受五个客户端消息
    if(res==-1)//失败了
    {
        close(sockfd);//如果忘记关闭,进程退出后也会自动关闭
        exit(1);
    }

    //4.客户端申请连接
    while(1)
    {
        int len=sizeof(caddr);//计算申请连接的客户端的套接字地址
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//可能会阻塞
        if(c==-1)//或者c<0
        {
            continue;//失败就继续接收
        }

        printf("accept client ip:%s,port=%d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
        char buff[128]={0};

        //5.服务器端接收连接
        int n=recv(c,buff,127,0);//也可以用read,读取期望从c客户端的buff中读取127个字符
        //recv也可能会阻塞,客户端不发数据
        printf("recv:(%d):%s\n",n,buff);

        //6.服务器端回复
        send(c,"ok",2,0);//回复c客户端,2个字符的内容:“OK”,标志位为0

        //7.关闭连接
        close(c);//关闭


    }


}

 //可以查看端口号

五、客户端编程:

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

int main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);//TCP固定搭配
    if(sockfd==-1)//楚错了
    {   
        exit(1);
    }    

    //可以用bind(),绑定端口,但不这样,作为客户端,你只需要知道服务器的IP端口,发起连接,自己的IP端口系统分配即可;如果自己绑定,恰好用了已绑定的端口,反而用不成了,所以一般不指定

    struct sockaddr_in saddr;//服务器的IP端口
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(6000);//连接的服务器的端口号是6000
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    
    int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)//失败:服务器没运行;没网络;端口写错
    {   
        printf("connect failed\n");
        close(sockfd);
        exit(1);
    }   

    printf("input\n");
    char buff[128]={0};
    fgets(buff,128,stdin);

    send(sockfd,buff,strlen(buff)-1,0);
    memset(buff,0,128);
    recv(sockfd,buff,127,0);
    printf("buff=%s\n",buff);
    close(sockfd);

    exit(0);
    
}

六、连接后运行结果

七、循环写入数据

1.客户端代码:

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

int socket_init();
int main()
{
    int sockfd=socket_init();
    if(sockfd==-1)
    {   
        exit(0);
    }   
    while(1)
    {   
        struct sockaddr_in caddr;
        int len=sizeof(caddr);
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);

        if(c<0)
        {
            continue;
        }
        printf("accept c=%d\n",c);
        while(1)
        {
            char buff[128]={0};
            int n=recv(c,buff,127,0);
            if(n<=0)//出错,或者对方关闭
            {
                break;
            }
            // printf("recv %s\n",buff);
            printf("accept client ip:%s,port=%d rece:%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buff);
            send(c,"OK",2,0);
        }
        close(c);
        printf("close\n");
    
    }   
    close(sockfd);
}

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

    struct sockaddr_in saddr;
    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)
    {   
        printf("bind err\n");
        return -1; 
    }   

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

2.服务器端代码:

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

int main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if (sockfd==-1)
    {   
        printf("socket err\n");
        exit(0);
    }   

    struct sockaddr_in saddr;
    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=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)
    {   
        printf("connect failed\n");
        exit(0);
    }   
    while(1)
    {   
        printf("input:\n");
        char buff[128]={0};
        fgets(buff,128,stdin);
        if(strncmp(buff,"end",3)==0)
        {
            break;
        }
        send(sockfd,buff,strlen(buff)-1,0);
        memset(buff,0,128);
        int n=recv(sockfd,buff,127,0);
        if(n<=0)
        {
            printf("服务器关闭了");
            break;
        }
        printf("buff=%s\n",buff);

    }   
    close(sockfd);
}

3.执行结果:

八、如果用两个终端同时运行./client会怎样?

 

1.结果如何:

//第一个运行的终端发送成功,并且受到ok,但是第二个没有收到ok,服务器端也没有显示

2.数据还能发送吗?

//可以

 //无法通过end退出第二个终端

//但在第一个终端发送end,两个终端上的都推出了;

//收到两个123,证明两个终端的内容都收到了

//在第一个终端发送end,第二个收到了ok

//即就是:在第一个终端的连接断开后,第二个连接上了,最后双方收到数据和OK

3.中间数据去哪了?

 第二个终端发送的数据三次握手是已经建立好了的,但数据由于第一个终端达成了连接,第二个终端就在已完成三次握手的队列中,数据在服务器端的接收缓冲区;

//netstat -natp

// 50166端口的client有匹配的服务器端

//50172端口的client后面对应服务器是空的

//50172只有监听套接字,没有对应的服务端

//50172第二列的数字6,代表发了大小为6的内容,证明数据在服务器端的接收缓冲区

//圈起来的第一个./service第二列的1代表还有一个连接没处理(就是第二个终端运行的那个client)

4.总结:

如果可以循环发送,一个在发送,另一个会阻塞,直到第一个退出

九、利用多线程,同时运行多客户端

1.代码:

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

int socket_init();

void* fun(void* arg)
{
    int c=(int)arg;
    while(1)
    {   
        char buff[128]={0};
        int n=recv(c,buff,127,0);
        if(n<=0)
        {
            break;
        }
        printf("buff(%d)=%s\n",c,buff);
        send(c,"OK",2,0);
    }   
    close(c);
    printf("close\n");
}

int main()
{
    int sockfd=socket_init();
    if(sockfd==-1)
    {   
        exit(0);
    }   
    while(1)
    {   
        struct sockaddr_in caddr;
        int len=sizeof(caddr);
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);

        if(c<0)
        {
            continue;
        }
        printf("accept c=%d\n",c);
        pthread_t id; 
        pthread_create(&id,NULL,fun,(void*)c);
    }   
    close(sockfd);
}

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

    struct sockaddr_in saddr;
    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)
    {   
        printf("bind err\n");
        return -1; 
    }   

    res=listen(sockfd,5);
    {   
        if(res==-1)
        {
            return -1; 
        }
        return sockfd;
    }   
}
~   

2.运行结果:

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值