一、TCP服务器和客户端设计

本文详细介绍TCP服务器编程的基础知识,包括创建套接字、绑定IP地址和端口、监听客户端连接请求、读写数据等核心步骤,并提供了一个完整的Linux环境下TCP服务器端的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 TCP通信原理

请添加图片描述

2 API接口函数

2.1 建立套接字接口

#include<sys/socket.h>
int socket(int family, int type, int protocol);
  • family:目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议)、AF_LOCAL(或称AF_UNIX,Unix域socket),AF_ROUTE等等。协议簇决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用IPv4地址(32位)与端口(16位)的组合,AF_UNIX决定了要用一个绝对路径名作为地址。
  • type:指明套接口类型,共有三种类型可选
       SOCK_STREAM(字节流套接口)
       SOCK_DGRAM(数据报套接口)
       SOCK_RAW(原始套接口)
  • protocol:顾名思意,就是指定协议。常用的协议有IPPRPTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。当protocol为0的时候,会自动选择type类型对应的默认协议。
  • return:若是成功则返回非负描述字,失败返回-1

2.2 为套接口分配一个本地IP和协议端口

#include<sys/socket.h>
int bind(int sockfd,
         const struct sockaddr* server,
         socklen_t addrlen);
  • sockfd:socket函数返回的描述字。
  • server:指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。

2.2.1 IPv4

struct sockaddr_in {
    sa_family_t    sin_family; /*协议族类型*/
    in_port_t      sin_port;/*端口*/
    struct in_addr sin_addr;/*IP地址*/
};

struct in_addr {
    uint32_t       s_addr;     
};

2.2.2 IPv6

struct sockaddr_in6 { 
    sa_family_t     sin6_family;    
    in_port_t       sin6_port;      
    uint32_t        sin6_flowinfo;  
    struct in6_addr sin6_addr;      
    uint32_t        sin6_scope_id;  
};

struct in6_addr { 
    unsigned char   s6_addr[16];    
};

2.2.3 Unix

#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;                
    char        sun_path[UNIX_PATH_MAX];   
};

2.3 等待客户端连接请求

#include<sys/socket.h>
int listen(int sockfd, int backlog);
  • sockfd:socket函数返回的套接口描述字。
  • backlog:规定了内核为此套接口排队的最大连接个数。
  • return:0表示成功,-1表示失败

2.4 连接客户端

#include<sys/socket.h>
int accept(int listenfd, 
           struct sockaddr *client, 
           socklen_t *addrlen);
  • listenfd:socket函数返回的套接口描述字。
  • client:客户端的套接口结构体地址。
  • addrlen:客户端的套接口结构体的长度。
  • return:该函数返回的是一个全新的套接口描述字符。
  • note:如果对客户端的信息不感兴趣,可以将第二和第三参数置空。

2.5 连接服务器

#include<sys/socket.h>
int connect(int sockfd, 
            const struct sockaddr *addr, 
            socklen_t addrlen)
  • sockfd:套接口描述字
  • addr:sockaddr: 结构体指针
  • addrlen:第二个参数结构体的大小

3 读写数据

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
  • brief:可以读取文件,指某一个已打开的文件中,读取一定量的字符,然后将这些读取字符,放入某一个预存的缓冲区内,供以后使用。
  • fd:文件描述符
  • buf:指缓冲区,即读取的数据会被放入到这个缓冲区中去。
  • count:传送count个字节到buf指针所指的内存中
  • return:返回值为实际读取到的字节数。如果返回值是0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。如果返回值-1,表明读取文件失败。
  • Note:读常规文件是不会阻塞的,如果读字符终端、网络的socket描述字,管道文件等,这些文件的缺省read都是阻塞的方式。

4 服务器端代码-Linux

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

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

void 
ProcessConnect(int new_sock){

  while(1){
    /*从客户端读取数据*/
    char buf[1024] = {0};
    ssize_t read_size = read(new_sock, buf, sizeof(buf) - 1);
    printf("read_size = %ld\n", read_size);
    if(read_size < 0){
      perror("read");
      continue;
    }
    /*没有读取到数据,说明客户端已关闭了连接*/
    if(read_size == 0){
      printf("[client %d] disconnect!\n", new_sock);
      close(new_sock);
      return;
    }
    buf[read_size] = '\0';
    printf("[client %d] %s\n", new_sock, buf);
    /*把响应写回到客户端*/
    write(new_sock, buf, strlen(buf));
  }
}

int
main(int argc, char *argv[]){
  if(argc != 3){
    printf("Uage ./server [ip] [port]\n");
    return 1;
  }
  /* 1.创建socket,AF_INET表示IPv4协议,SOCK_STREAM表示字节流传输协议*/
  int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
  if(listen_sock < 0){
    perror("socket");
    return 1;
  }
  /* 2.绑定ip地址和端口 */
  sockaddr_in server;
  server.sin_family = AF_INET;
  /*inet_addr将ip地址从点数转换成32位无符号长整形*/
  server.sin_addr.s_addr = inet_addr(argv[1]);
  server.sin_port = htons(atoi(argv[2]));
  int ret = bind(listen_sock, (sockaddr *)&server, sizeof(server));
  if(ret < 0){
    perror("bind");
    return 1;
  }
  /* 3.使用listen运行服务器被客户端连接 */
  ret = listen(listen_sock, 5);
  if(ret < 0){
    perror("listen");
    return 1;
  }
  /* 4.服务器初始化完成,进入事件循环 */
  printf("Server Init OK!\n");
  while(1){
    sockaddr_in peer;
    socklen_t len = sizeof(peer);
    /*系统会阻塞在这儿等待客户端连接*/
    int new_sock = accept(listen_sock, (sockaddr *)&peer, &len);
    /* 输出客户端IP地址
     * inet_ntoa 无符号32位IP转换成点型
     */    
    printf("Client Addr:%s,Port:%d Linked******\n", inet_ntoa(peer.sin_addr), peer.sin_port);

    if(new_sock < 0){
      perror("accept");
      continue;
    }
    printf("[client %d]connect!\n", new_sock);
    ProcessConnect(new_sock);
  }

  return 0;
}

参考1:TCP服务器和客户端程序设计
参考2:代码参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值