• TCP客户/服务器简单Socket程序


    建立一个 TCP 连接时会发生下述情形:

    1. 服务器必须准备好接受外来的连接。这通常通过调用 socket、bind 和 listen 这三个函数来完成,我们称之为被动打开。

    2. 客户通过调用 connect 发起主动打开,这导致客户TCP发送一个SYN(同步)分节,标识希望连接的服务器端口以及初始序号。通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。

    3. 服务器发送回一个包含服务器初始序号以及对客户端 SYN 段确认的 SYN + ACK 段作为应答,由于一个 SYN 占用一个序号,因此确认序号设置为客户端初始序号加 1。

    4. 客户端发送确认序号为服务器初始序号加 1 的 ACK 段,对服务器 SYN 段进行确认。

    这种交换至少需要三个分组,因此称之为TCP的三路握手。

    一旦TCP建立连接,客户/服务器之间便可以进行数据通信。

     1. 服务器端

    复制代码
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<sys/socket.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<netinet/in.h>
    #include <errno.h>  
    #define PORT 6666
    
    int main(int argc,char **argv)
    {
        int ser_sockfd,cli_sockfd;
        int err,n;
        int addlen;
        struct sockaddr_in ser_addr;
        struct sockaddr_in cli_addr;
        char recvline[200],sendline[200];
        
        ser_sockfd = socket(AF_INET,SOCK_STREAM,0);          //创建套接字
        if(ser_sockfd == -1)
        {
            printf("socket error:%s
    ",strerror(errno));
            return -1;
        }
        
        bzero(&ser_addr,sizeof(ser_addr));
        
        /*在待捆绑到该TCP套接口(sockfd)的网际套接口地址结构中填入通配地址(INADDR_ANY)
        和服务器的众所周知端口(PORT,这里为6666),这里捆绑通配地址是在告知系统:要是系统是
        多宿主机(具有多个网络连接的主机),我们将接受宿地址为任何本地接口的地址*/     
        ser_addr.sin_family = AF_INET;
        ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        ser_addr.sin_port = htons(PORT);
        
        //将网际套接口地址结构捆绑到该套接口
        err = bind(ser_sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));  
        if(err == -1)
        {
            printf("bind error:%s
    ",strerror(errno));
            return -1;
        }
        //将套接口转换为一个监听套接口,监听等待来自客户端的连接请求
        err = listen(ser_sockfd,5);                                      
        if(err == -1)
        {
            printf("listen error
    ");
            return -1;
        }
        
        printf("listen the port:
    ");
        
        while(1)
        {    
            addlen = sizeof(struct sockaddr);
            //等待阻塞,等待客户端申请,并接受客户端的连接请求
            //accept成功,将创建一个新的套接字,并为这个新的套接字分配一个套接字描述符
            cli_sockfd = accept(ser_sockfd,(struct sockaddr *)&cli_addr,&addlen);   
            if(cli_sockfd == -1)
            {
                printf("accept error
    ");
            }
            
            //数据传输
            while(1)
            {
                printf("waiting for client...
    ");
                n = recv(cli_sockfd,recvline,1024,0);
                if(n == -1)
                {
                    printf("recv error
    ");
                }
                recvline[n] = '';
                
                printf("recv data is:%s
    ",recvline);
                
                printf("Input your words:");
                scanf("%s",sendline);
                send(cli_sockfd,sendline,strlen(sendline),0);
            }
            close(cli_sockfd);
        }
        close(ser_sockfd);
        
        return 0;
    }
    复制代码

    1.首先通过 socket 函数创建套接字,此时套接字数据结构字段并未填充,在使用之前必须调用过程来填充对应字段,这里在地址结构中填入通配地址(INADDR_ANY),通配地址就是指定地址为 0.0.0.0 的地址,表示服务器接受机器上所有IP地址的连接,用于多IP机器上。这样无论客户 connect 哪个IP地址,服务器端都会接收到请求,即接受宿地址为任何本地接口的地址。如果是指定地址,那么机器只有 connect 这个地址才能成功。后面是填充端口号,如果指定为 0,则由系统随机选择一个未被使用的端口。

    2. bind 将没有指定端口的 socket(ser_sockfd)绑定到我们指定的端口上(通配地址+指定端口号),服务器是通过它们的众所周知端口被大家认识的。这样 socket(ser_sockfd)就与指定的端口产生了关联,即指向了指定端口。

    3. listen 将套接口转换为一个监听套接口,被动打开,允许监听客户端的连接请求,然后 accept 客户端的连接请求,没有请求则阻塞。

    5. accept 成功后,将创建新的套接字,并为新套接字分配一个套接口描述符,该套接字除了记录本地(服务器)的IP和端口号信息外,还记录了目的(客户)IP和端口号信息。服务器与客户的通信则是通过该新创建的套接字(已连接套接字)进行。


    2. 客户端

    复制代码
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<sys/socket.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<netinet/in.h>
    #define PORT 6666
    
    int main(int argc,char **argv)
    {
        int sockfd;
        int err,n;
        struct sockaddr_in addr_ser;
        char sendline[200],recvline[200];
        
        sockfd = socket(AF_INET,SOCK_STREAM,0);       //创建套接字
        if(sockfd == -1)
        {
            printf("socket error
    ");
            return -1;
        }
        
        bzero(&addr_ser,sizeof(addr_ser));     
        
        /*用通配地址和指定端口号装填一个网际接口地址结构*/ 
        addr_ser.sin_family = AF_INET;
        addr_ser.sin_addr.s_addr = htonl(INADDR_ANY);   
        addr_ser.sin_port = htons(PORT);                
        
        //TCP:客户(sockfd)向服务器(套接口地址结构)发起连接,主动请求
        //服务器的IP地址和端口号有参数addr_ser指定
        err = connect(sockfd,(struct sockaddr *)&addr_ser,sizeof(addr_ser));   
        if(err == -1)
        {
            printf("connect error
    ");
            return -1;
        }
        
        printf("connect with server...
    ");
        
        //数据传输
        while(1)
        {     
            printf("Input your words:");
            scanf("%s",sendline);
            
            send(sockfd,sendline,strlen(sendline),0);            
        
            printf("waiting for server...
    ");
        
            n = recv(sockfd,recvline,100,0);                      
            recvline[n] = ''; 
            
            printf("recv data is:%s
    ",recvline);
        }
        
        return 0;
    }
    复制代码

    1. 客户端同样通过 socket 创建套接字,TCP 客户通常不把IP地址捆绑到它的套接口上,当连接套接口时,内核将根据所用外出网络接口来确定源IP地址,并选择一个临时端口作为源端口。
    2. 用通配地址和指定端口填充的是待连接服务器端的套接字地址结购,这里采用的是通配地址,由于服务器端指定的是通配地址,即接受机器上所有IP地址的连接,同样客户也可向机器上任何IP地址发起连接,服务器端都会接收到。

    3. 客户端向服务器发起连接请求,connect 成功后,其请求连接的服务器的IP和端口号信息将会写入该套接字,这样该套接字也同时记录了本地和目的的IP地址和端口信息。也就可以进行通信了。

    结果如下:

  • 相关阅读:
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第5节 String类_7_字符串的转换相关方法
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第5节 String类_6_字符串的截取方法
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第5节 String类_5_字符串的获取相关方法
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第5节 String类_2_字符串的构造方法和直接创建
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第4节 ArrayList集合_19-ArrayList练习四_筛选集合
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第5节 String类_1_字符串概述和特点
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第4节 ArrayList集合_18-练习三_按指定格式打印集合的方法
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第4节 ArrayList集合_17-ArrayList练习二_存储自定义
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第4节 ArrayList集合_16-ArrayList练习一_存储随机数
    阶段1 语言基础+高级_1-3-Java语言高级_1-常用API_1_第4节 ArrayList集合_15-ArrayList集合存储基本数据
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8462505.html
Copyright © 2020-2023  润新知