Linux-网络-TCP-客户端-服务端

一.在传输层实现TCP的代码-服务端与客户端
1.服务端流程:

①socket(int domain(地址族ipv4/ipv6),int type(服务类型,tcp->流式服务,udp->数据报服务),int protocol(协议版本,目前恒为0))-->创建套接字

②bind(int sockfd(套接字描述符),const struct sockaddr *addr(使用哪个IP的哪个端口),socklen_t addrlen(指针指向空间的大小))-->指定IP与端口

③listen(int sockfd(套接字描述符),int backlog(监听队列的大小))-->创建监听队列

④accept(int sockfd(监听套接字),struct sockaddr* addr(套接字的地址结构,存放客户端的ip与端口),socklen_t* addrlen(存放客户端套接字的大小))-->接受客户端的连接

⑤recv(int sockfd(套接字描述符),void * buf(传到哪个buff中),size_t len(buff多大),int flags(标志位))-->接收客户端发送过来的数据

⑥send(int sockfd(监听套接字),const void * buf(待发送的数据),size_t len(发送数据的大小),int flags(标志位))-->给客户端回复数据

⑦close(int fd(监听套接字))-->关闭连接

2.客户端流程:

①socket(int domain(地址符ipv4/ipv6),int type(服务类型,tcp->流式服务,udp->数据报服务),int protocol(协议版本,目前恒为0))-->创建套接字

②connect(int sockfd(套接字描述符),const struct sockaddr* addr(套接字地址结构的指针,服务器的Ip和端口),socklen_t addrlen(结构的大小))-->向服务器端发起连接

③send(int sockfd(套接字),const void * buf(待发送的数据),size_t len(发送数据的大小),int flags(标志位))-->向服务器发送数据

④recv(int sockfd(套接字描述符),void * buf(传到哪个buff中),size_t len(buff多大),int flags(标志位))-->接收服务器返回回来的数据

⑤close(int fd(监听套接字))-->关闭连接

3.实现客户端与服务端的连接,客户端从键盘输入给服务端可以发送消息,服务端收到打印出来并返回ok,客户端收到后将ok也打印到屏幕上。

①服务端:

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

int main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字(视作打开的文件,返回值为文件描述符)-->套接字可以通过网络收发数据,AF_INET为IPv4,SOCK_STREAM为TCP的流式服务
    if(sockfd == -1)
    {
        printf("sockfd err\n");
        exit(1);
    }


    struct sockaddr_in saddr,caddr;//sockaddr_in为IPv4专用,一般为sockaddr。saddr为指定端口ip时使用,caddr为监听套接字时使用
    menset(&saddr,0,sizeof(saddr));//清空,本来saddr有四个结构,其中要求第四个结构要清为0,现在先将saddr全部清空为0,在重新定义其余三个结构即可
    saddr.sin_family=AF_INET;//与创建套接字使用的地址符相同
    saddr.sin_port=htons(6000);//指定端口号为6000,1024以内的值为知名端口号,1024-4096为保留端口,4096以上为临时端口。htons是将本主机的字节序列转换为网络字节序列,就是转换成大端形式,网络传输数据都是大端。
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//s_addr是无符号整形。inet_addr是将点分十进制的字符串形式的IP地址转换成无符号整形赋值给saddr。
    int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定,指定应用程序的IP端口。-->这里的强转是将IPV4专用的地址转成通用的地址  struct sockaddr_in 是IPV4专用
    if(res==-1)
    {
        printf("res err\n");
        exit(1);
    }


    res=listen(sockfd,5);//创建监听队列,sockfd为监听套接字
    if(res==-1)
    {
        printf("listrn err\n");
        exit(1);
    }

//以上三步都不会阻塞

    while(1)//接收连接
    {
        int len=sizeof(caddr);//accept接受连接,若没人连接则进行阻塞
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//c与sockfd交接套接字职责,c为链接套接字     -->当accept返回后,就会将返回客户端的ip地址和端口存放在caddr中,所以不用给caddr赋值,函数内部会给他赋值
        if(c<0)
        {
            printf("accept err\n");
            break;        
        }
        printf("accept c=%d\n",c);//打印套接字的值
//等待对方发送数据
        char buff[128]={0};
        int n=recv(c,buff,127,0);//接收客户端发来的数据,若没发来数据就会阻塞(可以换成read)
        printf("n=&s\n",buff);
//收到后给对方发消息
        send(c,"ok",2,0);//可以换成write()
        close(c);//关闭连接
    }
}

②客户端

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

int main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
    if(sockfd==-1)
    {
        exit(1);
    }

//连接服务器
    struct sockaddr_in saddr;//指定服务器的IP与端口
    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(saddr,(struct sockaddr*)&saddr,sizeof(saddr));//连接服务端
    if(res==-1)
    {
        exit(1);
    }

    char buff[128]={0};
    printf("input:\n");//打印出来说明连接成功了
    fgets(buff,128,stdin);

    send(sockfd,buff,strlen(buff),0);//给服务端发送数据

//接收服务端返回的数据
    menset(buff,0,sizeof(buff));//先将Buff清空,在用buff接收
    recv(sockfd,buff,127,0);
    printf("buff=&s\n",buff);
    close(sockfd);
    exit(0);
}
4.在上述代码中,客户端每次只能发送一次数据,可以添加循环,使客户端一直发送数据,直到客户端输入end后结束客户端。(客户端与服务端均需要添加循环)

①服务端:

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

int main()//循环的接收
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("sockfd err\n");
        exit(1);
    }

    struct sockaddr_in saddr,caddr;
    menset(&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("res err\n");
        exit(1);
    }

    res=listen(sockfd,5);
    if(res==-1)
    {
        printf("listrn err\n");
        exit(1);
    }

    while(1)
    {
        int len=sizeof(caddr);
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
        if(c<0)
        {
            printf("accept err\n");
            break;        
        }

        while(1)
        {
            printf("accept c=%d\n",c);
            char buff[128]={0};
            int n=recv(c,buff,127,0);
            if(n<=0)//客户端断开连接,n为收到字节的大小 -1为失败,0为客户端关闭了连接,关闭了会进行挥手通知服务端要关闭
            {
                break;
            }
            printf("n=&s\n",buff);

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

②客户端:

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

int main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        exit(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=connect(saddr,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)
    {
        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),0);
        menset(buff,0,sizeof(buff));
        recv(sockfd,buff,127,0);
        printf("buff=&s\n",buff);
    }
    close(sockfd);
    exit(0);
}
5.在改进的代码中,可以实现客户端与服务端循环的发送数据,但是当多个进程同时给一个服务端发送数据时,只能一个进程一个进程的发,无法做到多个进程一起发送数据。现在在服务端中进行fork操作(进程复制的方法),使得每一个客户端可以同时连接上服务端,可以做到同时发送数据。(只需要改动服务端,客户端与4中的相同)

①服务端:

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

int main()//多个客户端可以同时连接服务端,并且同时发送数据
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        printf("sockfd err\n");
        exit(1);
    }

    struct sockaddr_in saddr,caddr;
    menset(&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("res err\n");
        exit(1);
    }

    res=listen(sockfd,5);
    if(res==-1)
    {
        printf("listrn err\n");
        exit(1);
    }

    while(1)
    {
        int len=sizeof(caddr);
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//连接操作只在父进程中,每当有一个客户端请求连接时,fork出一个子进程,每个进程间相互独立,这样就可以实现多个客户端与服务端的连接。
        if(c<0)
        {
            printf("accept err\n");
            break;        
        }
        printf("accept c=%d\n",c);

        pid_t pid=fork();//创建子进程
        if(pid == 0)//在子进程中进行接收数据发送数据的操作
        {
            while(1)
            {
                char buff[128]={0};
                int n=recv(c,buff,127,0);
                if(n<0)
                {
                    break;
                }
                printf("n=&s\n",buff);
                send(c,"ok",2,0);
            }
            printf("client close\n");
            close(c);//关闭子进程的连接
            exit(0);//子进程退出
        }
        close(c);//关闭父进程中的套接字
    }
}

6.服务端中listen函数:在Listen中会创建2个监听队列,一个是未完成3次握手,一个是已完成3次握手。如listen(sockfd,5)-->在linux系统下此时这个5就是可以存放5个已完成三次握手。在其他一些系统上可能是未完成+已完成的次数。在不同的内核下可以存放已完成三次握手的大小不同。(uname -a可以查看内核版本)(linux下listen已完成差不多是n+1)

7.服务端中accept函数:当监听队列中有已完成三次握手的数据时,accept函数就会运行,否则就会阻塞。

8.send()函数和recv函数:send每次发送数据都是先将数据放到发送缓冲区内,recv每次收到数据都是先放到接受缓冲区内

            服务端                                                             客户端

 

recv打印缓冲区时,就是先将缓冲区现有的打印出来,可能有些数据还没有发过来,就是来了多少打印出多少。(netstat -natp(n是用数字显示ip地址  a是结果中会包含监听套接字  t是显示tcp连接  p是显示进程pid和名字))-->就是说发送的次数和接收的次数可能不是一一对应的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值