前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实现基于TCP的服务端/客户端

实现基于TCP的服务端/客户端

作者头像
玖柒的小窝
修改2021-12-24 11:40:06
1.2K0
修改2021-12-24 11:40:06
举报
文章被收录于专栏:各类技术文章~

服务端套接字创建过程

第一步:调用socket函数创建套接字

代码语言:javascript
复制
//成功时返回文件表述符,失败时返回-1
int socket(int __domain, int __type, int __protocol)
  • domain:套接字使用的协议族(Protocol Family)信息;
  • type:套接字数据传输类型信息;
  • protocol:计算机间通信中使用的协议信息;

协议族(Protocol Family)

代码语言:javascript
复制
 Name         Purpose                                    Man page
 AF_UNIX      Local communication                        unix(7)
 AF_LOCAL     Synonym for AF_UNIX
 AF_INET      IPv4 Internet protocols                    ip(7)
 AF_IPX       IPX - Novell protocols
 AF_INET6     IPv6 Internet protocols                    ipv6(7)
 AF_PACKET    Low-level packet interface                 packet(7)

套接字类型(Type):是指套接字的传输方式

  • 面向连接的套接字(SOCK_STREAM)
    • 传输过程中数据不会消失
    • 按序传输数据
    • 传输的数据不存在数据边界(Boundary):调用了三次write传递了100字节,接受者仅一次read接收了全部
  • 面向消息的套接字(SOCK_DGRAM)
    • 强调快速传输而非传输顺序
    • 传输的数据可能丢失也可能损毁
    • 传输的数据有数据边界
    • 限制每次传输的数据大小

协议的最终选择

代码语言:javascript
复制
/* Standard well-defined IP protocols.  */
enum
  {
    IPPROTO_IP = 0,	   /* Dummy protocol for TCP.  */
#define IPPROTO_IP		IPPROTO_IP
    IPPROTO_ICMP = 1,	   /* Internet Control Message Protocol.  */
#define IPPROTO_ICMP		IPPROTO_ICMP
    IPPROTO_IGMP = 2,	   /* Internet Group Management Protocol. */
#define IPPROTO_IGMP		IPPROTO_IGMP
    IPPROTO_IPIP = 4,	   /* IPIP tunnels (older KA9Q tunnels use 94).  */
#define IPPROTO_IPIP		IPPROTO_IPIP
    IPPROTO_TCP = 6,	   /* Transmission Control Protocol.  */
#define IPPROTO_TCP		IPPROTO_TCP
    IPPROTO_EGP = 8,	   /* Exterior Gateway Protocol.  */
#define IPPROTO_EGP		IPPROTO_EGP
    IPPROTO_PUP = 12,	   /* PUP protocol.  */
#define IPPROTO_PUP		IPPROTO_PUP
    IPPROTO_UDP = 17,	   /* User Datagram Protocol.  */
#define IPPROTO_UDP		IPPROTO_UDP
    IPPROTO_IDP = 22,	   /* XNS IDP protocol.  */
#define IPPROTO_IDP		IPPROTO_IDP
    IPPROTO_TP = 29,	   /* SO Transport Protocol Class 4.  */
#define IPPROTO_TP		IPPROTO_TP
    IPPROTO_DCCP = 33,	   /* Datagram Congestion Control Protocol.  */
#define IPPROTO_DCCP		IPPROTO_DCCP
    IPPROTO_IPV6 = 41,     /* IPv6 header.  */
#define IPPROTO_IPV6		IPPROTO_IPV6
    IPPROTO_RSVP = 46,	   /* Reservation Protocol.  */
#define IPPROTO_RSVP		IPPROTO_RSVP
    IPPROTO_GRE = 47,	   /* General Routing Encapsulation.  */
#define IPPROTO_GRE		IPPROTO_GRE
    IPPROTO_ESP = 50,      /* encapsulating security payload.  */
#define IPPROTO_ESP		IPPROTO_ESP
    IPPROTO_AH = 51,       /* authentication header.  */
#define IPPROTO_AH		IPPROTO_AH
    IPPROTO_MTP = 92,	   /* Multicast Transport Protocol.  */
#define IPPROTO_MTP		IPPROTO_MTP
    IPPROTO_BEETPH = 94,   /* IP option pseudo header for BEET.  */
#define IPPROTO_BEETPH		IPPROTO_BEETPH
    IPPROTO_ENCAP = 98,	   /* Encapsulation Header.  */
#define IPPROTO_ENCAP		IPPROTO_ENCAP
    IPPROTO_PIM = 103,	   /* Protocol Independent Multicast.  */
#define IPPROTO_PIM		IPPROTO_PIM
    IPPROTO_COMP = 108,	   /* Compression Header Protocol.  */
#define IPPROTO_COMP		IPPROTO_COMP
    IPPROTO_SCTP = 132,	   /* Stream Control Transmission Protocol.  */
#define IPPROTO_SCTP		IPPROTO_SCTP
    IPPROTO_UDPLITE = 136, /* UDP-Lite protocol.  */
#define IPPROTO_UDPLITE		IPPROTO_UDPLITE
    IPPROTO_MPLS = 137,    /* MPLS in IP.  */
#define IPPROTO_MPLS		IPPROTO_MPLS
    IPPROTO_RAW = 255,	   /* Raw IP packets.  */
#define IPPROTO_RAW		IPPROTO_RAW
    IPPROTO_MAX
  };

第二步:调用bind函数分配IP地址和端口号

代码语言:javascript
复制
//成功时返回0,失败时返回-1
int bind(int __fd, const struct sockaddr *__addr, socklen_t __len)
  • __fd:要分配地址信息(IP地址和端口号)的套接字文件表述符。
  • __addr:存有地址信息的结构体变量地址值
  • __len:第二个结构体变量的长度

第三步:调用listen函数转为可接收请求状态

代码语言:javascript
复制
//成功时返回0,失败时返回-1
int listen(int __fd, int __n)
  • __fd:希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字参数成为服务器端套接字(监听套接字)。
  • __n:连接请求等待队列的长度。

第四步:调用accept函数受理连接请求

代码语言:javascript
复制
//成功时返回0,失败时返回-1
int accept(int __fd, struct sockaddr *__restrict__ __addr, socklen_t *__restrict__ __addr_len)
  • __fd:服务器套接字的文件描述符。
  • __addr:保存发起连接请求的客户端地址信息地变量地址值,调用函数后向传递来的地址变量参数填充客户端的地址信息。
  • __addr_len:第二个参数__addr结构体的长度,但是存有长度的变量地址。函数调用完后,该变量即被填入客户端地址长度。

客户端创建套接字过程

第一步:调用socket函数创建套接字

代码语言:javascript
复制
//成功时返回文件表述符,失败时返回-1
int socket(int __domain, int __type, int __protocol)

第二步:调用socket函数向服务器端发送连接请求

代码语言:javascript
复制
//成功时返回0,失败时返回-1
int connect(int __fd, const struct sockaddr *__addr, socklen_t __len)
  • __fd:客户端套接字文件表述符。
  • __addr:保存目标服务器端地址信息的变量地址值。
  • __len:以字节为单位传递已传递给第二个结构体参数__addr地址变量长度。

客户端调用connect函数后服务端接收连接请求(记录到等待队列)或发生断网等异常情况而中断连接请求才会返回(完成函数调用)。客户端的IP地址和端口号在调用connect函数时由内核自动分配,无需调用标记的bind函数进行分配。

总体流程就是:服务器端创建套接字后联系调用bind、listen函数进入等待状态,客户端通过调用connect函数发起连接请求,需要注意的是,客户端只能等到服务器端调用listen函数后才能调用connect函数。同时要清楚,客户端调用connect前,服务器端可能先调用了accept函数。当然,此时服务器端在调用accept函数时进入了阻塞状态,直到客户端调用connect函数为止。

TCP套接字中的I/O缓冲

  • I/O缓冲在每个TCP套接字中单独存在;
  • I/O缓冲在创建套接字时自动生成;
  • 即使关闭套接字也会继续传递输出缓冲中遗留的数据;
  • 关闭套接字将丢失输入缓冲中的数据;

编写

echosrv.c

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char** argv) {
    // 1. 创建套接字
    int listenfd;
                          //协议族    套接字类型    协议类型
    if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
       perror("socket");
    }

    // 2. 分配套接字地址
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof servaddr);
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    // servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // inet_aton("127.0.0.1", &servaddr.sin_addr);

    int on = 1;
    // 确保time_wait状态下同一端口仍可使用
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0)
    {
        perror("setsockopt");
    }

    // 3. 绑定套接字地址
    if (bind(listenfd, (struct sockaddr*) &servaddr, sizeof servaddr) < 0) {
        perror("bind");
    }
    // 4. 等待连接请求状态
    if (listen(listenfd, SOMAXCONN) < 0) {
        perror("listen");
    }
    // 5. 允许连接
    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof peeraddr;
    int connfd;
    if ((connfd = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen)) < 0) {
        perror("accept");
    }

    printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));
    printf("port = %d\n", ntohs(peeraddr.sin_port));

    // 6. 数据交换
    char recvbuf[1024];
    while (1)
    {
        memset(recvbuf, 0, sizeof recvbuf);
        int ret = read(connfd, recvbuf, sizeof recvbuf);
        if (ret == 0)
        {
            printf("client close\n");
            break;
        } else if (ret == -1)
        {
            perror("read");
        }
        fputs(recvbuf, stdout);
        write(connfd, recvbuf, ret);
    }

    // 7. 断开连接
    close(connfd);
    close(listenfd);
    return 0;
}

echocli.c

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        perror("socket");
    }

    // 2. 分配套接字地址
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof servaddr);
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    // servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // inet_aton("127.0.0.1", &servaddr.sin_addr);

    // 3. 请求链接
    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
        perror("connect");
    }

    // 4. 数据交换
    char recvbuf[1024] = {0};
    char sendbuf[1024] = {0};
    while (fgets(sendbuf, sizeof sendbuf, stdin) != NULL)   // 键盘输入获取
    {
//        memset(recvbuf, 0, sizeof recvbuf);
//        memset(sendbuf, 0, sizeof sendbuf);
        write(sockfd, sendbuf, sizeof sendbuf); // 写入服务器
        int ret = read(sockfd, recvbuf, sizeof recvbuf);    // 服务器读取
        if (ret == 0)
        {
            printf("server close\n");
            break;
        } else if (ret == -1)
        {
            perror("read");
        }
        fputs(recvbuf, stdout); // 服务器返回数据输出

        // 清空
        memset(recvbuf, 0, sizeof recvbuf);
        memset(sendbuf, 0, sizeof sendbuf);
    }

    // 5. 断开连接
    close(sockfd);

    return 0;
}

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 服务端套接字创建过程
  • 客户端创建套接字过程
  • TCP套接字中的I/O缓冲
  • 编写
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档