Linux 网络 tcp C/S通信模型

C/S模型就是server 与 client 的模型

TCP服务器模型流程图                                                               TCP 客户端模型流程图:

                

  函数使用:

(1)创建一个网络通信套接字描述符  int socket(int domain, int type, int protocol);  

  参数:domain : 协议系列,常用的是 AF_INET 表示IPV4 

                  

        type : 常用的两个

        SOCK_STREAM    流式套接字 TCP 常用

        SOCK_DGRAM  数据报套接字 UDP 常用

    

    protocol:一般为0

    

  返回值:通信过程中使用的socket(一个文件描述符)

(2)把socket与 ip, 端口绑定一起     int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

   参数 :sockfd :通过socket 函数创建返回的通信文件描述符

       addr:addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号协议类型等

      addrlen: addrLen : sizeof (struct sockaddr_in) 是第二个参数addr结构所占的有效长度

 

struct sockaddr结构 和 struct sockaddr_in结构

struct sockaddr {
  sa_family_t sa_family; //协议族
  char sa_data[14];  // 14字节协议地址
}

struct sockaddr_in{
  u_short sin_family;// 地址族, AF_INET,2 bytes
  u_short sin_port; // 端口,2 bytes
  struct in_addr sin_addr; // IPV4地址结构,4 bytes
  char sin_zero[8]; // 8 bytes unused,作为填充
};

 在bind时使用 struct sockaddr_in 结构进行初始化,然后通过 struct sockaddr 强制类型转换

sockaddr_in 与 sockaddr 结构的区别

 

(3) 监听绑定了ip和port的socket :int listen(int sockfd, int backlog);

  参数:sockfd:监听连接的套接字

     backlog 指定了完全成功连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。此参数现在基本已经不用。

  返回值: 0 或 -1

完成listen()调用后,socket变成了监听

(4)阻塞等待连接 :int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  参数: sockfd : 监听套接字

      addr : 对方地址

     addrlen:地址长度

  返回值:已建立好连接的套接字 或 -1

      

(5) 发送数据

  ssize_t send(int sockfd, const void *buf, size_t len, int flags);

  参数: sockfd :向哪个socket发送数据

     buf :存放数据缓存首地址

     len :发送的数据长度

     flags:发送方式(通常为0)

  返回值:成功:实际发送的字节数     失败:-1, 并设置errno

 (6) 接收数据

  ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  参数:socket:向哪个socket发送数据

      buf : 发送缓冲区首地址

      length : 发送的字节数

      flags : 接收方式(通常为0)

  返回值: 成功:实际接收的字节数      失败:-1, 并设置errno

 (7) 客户端连接服务器的函数

  int connect(int  sockfd,  const struct sockaddr   *addr,  socklen_t  addrlen);

  参数:sockfd : socket返回的文件描述符

     serv_addr : 服务器端的地址信息 

     addrlen : serv_addr的长度

  返回值:0 或 -1

(8) 关闭通信socket    int close(int fd);

 编写TCP 的server和client 通信程序,服务器监听接收数据

  server.c文件:

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

#define MAX_BUF          1024
int main(int argc, const char *argv[])
{
    char buf[MAX_BUF] = {0};//接收数据缓存
    int server_sock_fd; //服务器,socket描述符
    int client_sock_fd; //链接的客户端,socket 描述符
    ssize_t tybes;

    struct sockaddr_in server_addr;//存放服务器ip和端口
    struct sockaddr_in client_addr;//存放客户端ip和端口
    socklen_t len; 
    
    len = sizeof(server_addr);
    bzero(&server_addr,len);//清零
    server_sock_fd = socket(AF_INET, SOCK_STREAM,0);//创建socket,返回socket描述符
    if(server_sock_fd < 0)
    {
        perror("socket fail ");
        exit(1);
    }

    //填充 地址和端口结构
    server_addr.sin_family = AF_INET; //协议族
//    server_addr.sin_port = htons(atoi(argv[2])); //服务器端口    //这两行是通过执行程序时传入参数ip和port
//    server_addr.sin_addr.s_addr = inet_addr(argv[1]);//IP地址
    server_addr.sin_port = htons(atoi("8080")); //服务器端口 , htons 把主机字节序转为网络字节序,atoi 把字符数据转为整数
    server_addr.sin_addr.s_addr = inet_addr("192.168.3.132");//IP地址 ,inet_addr把字符串ip转为网络字节序整数
    if(bind(server_sock_fd, (struct sockaddr*)&server_addr, len) < 0) //绑定服务器 ip和port
    {
        perror("fail bind ");
        exit(1);
    }

    if( listen(server_sock_fd,10) < 0 ) //监听 服务器 
    {
        perror("fail listen ");
        exit(1);
    }
    
    while(1)
    {
        client_sock_fd = accept(server_sock_fd,(struct sockaddr*)&client_addr,&len);//阻塞等待客户端链接
        if(client_sock_fd < 0)
        {
            perror("client socket fail ");
            exit(1);
        }
        printf("client ip %s   port %u
",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); //打印链接的ip 和 port
        while(1)
        {
            memset(buf,0,1024);
        //    tybes = recv(client_sock_fd,buf,10,0);
            tybes = read(client_sock_fd,buf,10);
            if(tybes <= 0) //断开链接或者出错
            {
                puts("recv error !");
                break;
            }
            printf("recv data : %s",buf);//IO缓冲 加 换行输出
            if(strstr(buf,"quit") != NULL ) //接收到 “quit” 断开连接的客户端,等待下一个连接
            {
                close(client_sock_fd);
                break;
            }
        }

    }
    close(server_sock_fd);
    return 0;
}

  client.c 文件;

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

int main(int argc, const char *argv[])
{
    int client_sock_fd;
    char buf[20] = {0};
    ssize_t send_tybes;
    struct sockaddr_in server_addr; 
    socklen_t len;
    len = sizeof(server_addr);
    bzero(&server_addr,len); //清零

    client_sock_fd = socket(AF_INET,SOCK_STREAM,0); //创建客户端 socket 
    if(client_sock_fd < 0)
    {
        perror("client_sock_fd fail ");
        exit(1);
    }

    //填充服务器 地址
    server_addr.sin_family = AF_INET;
//    server_addr.sin_port = htons(atoi(argv[2]));
//    server_addr.sin_addr.s_addr = (inet_addr(argv[1]));
    server_addr.sin_port = htons(atoi("8080"));
    server_addr.sin_addr.s_addr = (inet_addr("192.168.3.132"));

    if( connect(client_sock_fd, (struct sockaddr*)&server_addr, len) < 0) //连接服务器
    {
        perror("connect fail ");
        exit(1);
    }

    while(1) 
    {
        fgets(buf,10,stdin); //终端读取数据
        send_tybes = send(client_sock_fd,buf,10,0);
        if(send_tybes <= 0) //接收数据失败或者断开连接
        {
            printf("send fail !");
            break;
        }
        if(strstr(buf,"quit") != NULL) //输入 “quit” 断开连接
        {
            break;
        }
    }
    close(client_sock_fd);
    return 0;
}

测试:此时,服务器只是接收数据,不能发送数据,因为接收和终端获取数据是两个阻塞函数,需要开启多进程或者多线程处理两个阻塞问题

  

原文地址:https://www.cnblogs.com/electronic/p/10981614.html