首页 > 编程语言 >C++中TCP服务端程序

C++中TCP服务端程序

时间:2024-09-28 22:20:53浏览次数:11  
标签:addr int SOCK C++ socket TCP 接字 服务端 struct

服务端

创建流程

一、 调用socket函数创建监听socket

socket套接字:表示通信的端点。就像用电话通信,套接字相当于电话,IP地址相当于总机号码,而端口号则相当于分机号码。

int socket(int domain, int type, int protocol);

1. domain(协议族):
指定通信的协议族,常见的有:
- AF_INET:IPv4 协议。
- AF_INET6:IPv6 协议。
- AF_LOCAL 或 AF_UNIX:本地通信(同一台计算机上的进程间通信)。
- AF_PACKET:底层网络通信,允许访问物理层,如以太网帧。

2. type(套接字类型):
指定套接字的类型,常见的有:
- SOCK_STREAM:面向连接的字节流套接字(TCP),保证数据的顺序和可靠传输。
- SOCK_DGRAM:无连接的数据报套接字(UDP),不保证数据的顺序和可靠性。
- SOCK_SEQPACKET:有序数据包套接字(面向消息的字节流套接字)。
- SOCK_RAW:原始套接字,用于操作网络层协议(如 ICMP、IP)。

最常见的选项是 SOCK_STREAM 和 SOCK_DGRAM。

3. protocol(协议):

  • 指定使用的具体协议,通常设置为 0,让系统选择默认协议:
  • 当 type 为 SOCK_STREAM 时,默认是 TCP 协议。
  • 当 type 为 SOCK_DGRAM 时,默认是 UDP 协议。

函数返回值
成功时,返回一个非负的文件描述符(Socket 描述符),用于后续的网络通信。
失败时,返回 -1,并设置 errno 以指示错误原因。

所以创建一个socket:

    // 创建一个监听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);	
	//常见的AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域
	//套接口可能的类型有:SOCK_STREAM字节流、SOCK_DGRAM数据报、SOCK_SEQPACKET有序分组、SOCK_RAW原始套接口
	//传输协议TCP/UDP,这里默认0
    if (listenfd == -1) {
        cout << " create listen socket error " << endl;
        return -1;
    }

二、创建struct sockaddr_in结构体,并调用bind函数

struct sockaddr_in 是一种用于存储 IPv4 地址信息的结构体,在网络编程中用于指定套接字的地址信息,例如绑定地址、连接地址等。这个结构体是sockaddr的特化版本,专门用于 IPv4 协议。它通常与bind()、connect()、accept()等函数一起使用。

struct sockaddr_in 在 <netinet/in.h> 头文件中定义,通常如下:

struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族(Address Family)
    in_port_t      sin_port;     // 端口号(Port Number)
    struct in_addr sin_addr;     // IP 地址(Internet Address)
    char           sin_zero[8];  // 填充字段(Padding, 保持结构体与 sockaddr 的长度一致)
};

其中的参数定义如下:

1. sin_family:
+ 类型:sa_family_t
+ 说明:指定地址族,必须设置为 AF_INET,表示使用 IPv4 协议。

2. sin_port:

  • 类型:in_port_t(通常是 uint16_t)
  • 说明:指定端口号,端口号使用网络字节序(大端字节序)。在赋值时,需使用 htons() 函数将主机字节序转换为网络字节序。例如,端口号 8080 设置为:sin_port = htons(8080);

3. sin_addr:

  • 类型:struct in_addr

  • 说明:用于存储 IPv4 地址,同样需要使用网络字节序。

  • struct in_addr 的定义如下:

    struct in_addr {
        uint32_t s_addr;  // 32 位 IPv4 地址
    };
    
  • 常见赋值方式:

      sin_addr.s_addr = inet_addr("127.0.0.1"); // 使用字符串表示的 IP 地址
      sin_addr.s_addr = htonl(INADDR_ANY); // 使用 INADDR_ANY 绑定到所有可用接口(通常用于服务器)
    

4. sin_zero:

  • 类型:char[8]
  • 说明:保留字段,用于填充 sockaddr_in 结构体,使其大小与 struct sockaddr 相同。通常应将其置为 0,不会使用其中的数据。

调用bind函数进行绑定:

    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(8081);
	//如果只想在本机上进行访问,bind函数地址可以使用本地回环地址
	//如果只想被局域网的内部机器访问,那么bind函数地址可以使用局域网地址
	//如果希望被公网访问,那么bind函数地址可以使用INADDR_ANY or 0.0.0.0
    if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {
        cout << "bind listen socket error" << endl;
        return -1;
    }

三、启动listen监听

listen() 函数用于将一个套接字(Socket)设置为监听状态,使其可以接受来自客户端的连接请求。它是 TCP 服务器中必不可少的步骤之一,在创建套接字和绑定地址之后调用。

int listen(int sockfd, int backlog);

1. sockfd:

  • 类型:int
  • 说明:指定用于监听的套接字文件描述符,这个套接字必须已经使用 socket() 创建并通过 bind() 绑定了地址和端口。

2. backlog:

  • 类型:int

  • 说明:指定等待连接队列的最大长度,即在 Socket 被 accept() 接受之前可以排队等待的最大客户端连接数量。

  • 常用值:

    - SOMAXCONN:一个系统定义的常量,表示系统允许的最大连接数。这是推荐的值,因为它可以自动调整到系统允许的最大值。
    - 或者可以设置为一个具体的数字(如 5、10),但是 SOMAXCONN 更常见。
    

返回值:
成功时返回 0。
失败时返回 -1,并设置 errno 以指示错误原因。

示例代码:

    // 启动监听
    if (listen(listenfd, SOMAXCONN) == -1) {
        cout << "listen error" << endl;
        return -1;
    }
	cout << "开始监听" << endl;

四、调用accept函数接受连接

当有客户端连接请求时,调用accept函数接受连接,产生一个新的socket(与客户端通信的socket)。accept() 函数用于从监听套接字(通常是服务器端的)中提取一个客户端的连接请求,并为这个连接创建一个新的套接字。这是服务器接受客户端连接的关键步骤,调用 accept() 后,服务器可以与客户端进行数据交换。

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

1. sockfd(监听套接字描述符):

  • 类型:int
  • 说明:这是服务器端用于监听的套接字描述符,必须通过 socket()、bind() 和 listen() 配置好并处于监听状态。通常是 listenfd。

2.addr(客户端地址结构体):

  • 类型:struct sockaddr *
  • 说明:用于存储客户端的地址信息,通常是 sockaddr_in 类型的指针,需要将其强制转换为 struct sockaddr * 类型。
  • 服务器通过这个结构体获取客户端的 IP 地址和端口号。

3. addrlen(地址结构体长度):

  • 类型:socklen_t *(通常是 int * 或 unsigned int *)
  • 说明:一个指向整数的指针,指定 addr 结构体的大小,调用 accept() 后会被填充为实际的地址长度。

返回值:

  • 成功时:返回一个新的套接字描述符 clientfd,专用于与该客户端的通信。
  • 失败时:返回 -1,并设置 errno 以指示错误原因。

示例代码:

//创建一个临时的客户端socket
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
// 接受客户端连接
int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);

五、与客户端通信

(1)接受消息

recv() 函数用于从套接字中接收数据。它是一个阻塞函数,会一直等待数据的到来,直到接收到数据或发生错误。

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

1. sockfd(套接字描述符):

  • 类型:int
  • 说明:用于接收数据的套接字描述符,这里通常是 accept() 返回的客户端套接字描述符 clientfd。

2. buf(接收缓冲区):

  • 类型:void *
  • 说明:指向存储接收到的数据的缓冲区,这里是 recvBuf。

3. len(缓冲区大小):

  • 类型:size_t
  • 说明:指定接收缓冲区的最大字节数,这里是 32,表示最多接收 32 个字节的数据。

4. flags(标志):

  • 类型:int
  • 说明:用于控制接收行为,常用值:
  • 0:默认行为,阻塞接收数据。
  • MSG_DONTWAIT:非阻塞接收。
  • MSG_PEEK:查看数据但不从缓冲区移除数据。

返回值

  • 成功时,返回实际接收到的字节数。
  • 返回 0 表示对方关闭了连接。
  • 返回 -1 表示发生错误,并设置 errno 以指示错误原因。
(2)发送消息

send() 函数用于向套接字发送数据,将数据从指定的缓冲区发送到对端。

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

1. sockfd(套接字描述符):

  • 类型:int

  • 说明:用于发送数据的套接字描述符,这里是 clientfd。
    ** 2. buf(发送缓冲区):**

  • 类型:const void *

  • 说明:指向包含要发送的数据的缓冲区,这里是 recvBuf。

3. len(发送数据的大小):

  • 类型:size_t

  • 说明:指定发送的数据字节数,这里是 strlen(recvBuf),表示发送 recvBuf 缓冲区中字符串的实际长度。
    4. flags(标志):

  • 类型:int

  • 说明:用于控制发送行为,常用值:

      - 0:默认行为,阻塞发送数据。
      - MSG_DONTWAIT:非阻塞发送。
      - MSG_NOSIGNAL:避免发送时产生 SIGPIPE 信号。
    

返回值

  • 成功时,返回实际发送的字节数。
  • 返回 -1 表示发送失败,并设置 errno 以指示错误原因。

示例代码:

//将接受到消息返回给客户端
if (clientfd != -1) {
	   char recvBuf[32] = {0};
	   // 从客户端接受数据
	   int ret = recv(clientfd, recvBuf, 32, 0);
	   if (ret > 0) {
	       cout << "recv data from cilent , data:" << recvBuf << endl;
	       // 将接收到的数据原封不动地发给客户端
	       ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
	       if (ret != strlen(recvBuf)) {
	           cout << "send data error" << endl;
	       } else {
	           cout << "send data to client successfully, data " << recvBuf <<endl;
	       }
	   } else {
	       cout << "recv data error" <<endl;
	   }
	   close(clientfd);
}

六、调用close函数关闭socket

close() 函数用于关闭一个打开的文件描述符,包括套接字文件描述符。它是网络编程中管理资源的关键步骤,用于释放套接字所占用的系统资源,断开连接并停止进一步的通信。

int close(int fd);

1. fd(文件描述符):

  • 类型:int
  • 说明:指定需要关闭的文件描述符。在网络编程中,这个文件描述符通常是一个套接字,例如监听套接字 listenfd 或连接套接字 clientfd。

返回值

  • 成功时:返回 0。
  • 失败时:返回 -1,并设置 errno 以指示错误原因。

示例代码:

// 关闭监听socket
close(listenfd);

全部代码

#include <iostream>
#include <sys/types.h>	//基本系统数据类型
#include <arpa/inet.h>	//网络信息转换
#include <unistd.h>		//POSIX系统API访问
#include <string.h>

using namespace std;
int main() {

    // 创建一个监听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);	
	//常见的AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域
	//套接口可能的类型有:SOCK_STREAM字节流、SOCK_DGRAM数据报、SOCK_SEQPACKET有序分组、SOCK_RAW原始套接口
	//传输协议TCP/UDP,这里默认0
    if (listenfd == -1) {
        cout << " create listen socket error " << endl;
        return -1;
    }
    // 初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(8081);
	//如果只想在本机上进行访问,bind函数地址可以使用本地回环地址
	//如果只想被局域网的内部机器访问,那么bind函数地址可以使用局域网地址
	//如果希望被公网访问,那么bind函数地址可以使用INADDR_ANY or 0.0.0.0
    if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {
        cout << "bind listen socket error" << endl;
        return -1;
    }
    // 启动监听
    if (listen(listenfd, SOMAXCONN) == -1) {
        cout << "listen error" << endl;
        return -1;
    }
	cout << "开始监听" << endl;

    while (true) {
        // 创建一个临时的客户端socket
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);
        // 接受客户端连接
        int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);
        if (clientfd != -1) {
            char recvBuf[32] = {0};
            // 从客户端接受数据
            int ret = recv(clientfd, recvBuf, 32, 0);
            if (ret > 0) {
                cout << "recv data from cilent , data:" << recvBuf << endl;
                // 将接收到的数据原封不动地发给客户端
                ret = send(clientfd, recvBuf, strlen(recvBuf), 0);
                if (ret != strlen(recvBuf)) {
                    cout << "send data error" << endl;
                } else {
                    cout << "send data to client successfully, data " << recvBuf <<endl;
                }
            } else {
                cout << "recv data error" <<endl;
            }
            close(clientfd);
        }
    }

    // 关闭监听socket
    close(listenfd);
    return 0;
}

注:
处理多个TCP连接的可以查看:使用epoll处理多个TCP线程连接

标签:addr,int,SOCK,C++,socket,TCP,接字,服务端,struct
From: https://blog.csdn.net/weixin_45429924/article/details/142621624

相关文章

  • C++ 循环 && C++ 判断
    循环类型循环控制语句无限循环判断语句?:运算符有的时候,可能需要多次执行同一块代码。一般情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执行一个语句或语......
  • 华为OD机试2024年E卷-转骰子[200分]( Java | Python3 | C++ | C语言 | JsNode | Go )实
    题目描述骰子是一个立方体,每个面一个数字,初始为左1,右2,前3(观察者方向),后4,上5,下6,用123456表示这个状态,放置在平面上,可以向左翻转(用L表示向左翻转1次),可以向右翻转(用R表示向右翻转1次),可以向前翻转(用F表示向前翻转1次),可以向后翻转(用B表示向后翻转1次),可以逆时针旋转(......
  • 华为OD机试2024年E卷-矩阵匹配[200分]( Java | Python3 | C++ | C语言 | JsNode | Go )
    题目描述从一个N*M(N≤M)的矩阵中选出N个数,任意两个数字不能在同一行或同一列,求选出来的N个数中第K大的数字的最小值是多少。输入描述输入矩阵要求:1≤K≤N≤M≤150输入格式:NMKN*M矩阵输出描述N*M的矩阵中可以选出M!/N!种组合数组,每个组合......
  • C++多线程与并发类面试题
    题目来源:https://subingwen.cn/cpp/thread/https://mp.weixin.qq.com/s?__biz=Mzg4NDQ0OTI4Ng==&mid=2247489580&idx=1&sn=b9ac83040601230ff897f3394e956cea&chksm=cfb95145f8ced8536d5dcfa7d3165e3a51f5cb40e52f699745df0d8f71e4f7591674cd5cf156&token=......
  • C++入门基础知识90(实例)——实例15【求两数的最大公约数】
    成长路上不孤单......
  • C++入门基础知识89(实例)——实例14【创建各类三角形图案】
    成长路上不孤单......
  • C++:模版初阶
    目录一、泛型编程二、函数模版概念格式原理 实例化模版参数的匹配原则 三、类模版定义格式实例化一、泛型编程   如何实现一个通用的交换函数呢?voidSwap(int&left,int&right){ inttemp=left; left=right; right=temp;}voidSwap(d......
  • C++ day02(函数、类和对象、封装、构造函数、析构函数)
    目录【1】函数1》内联函数inline 2》函数重载overload  3》函数的参数默认(缺省)值 4》哑元函数【2】类和对象1》类的定义 2》创建对象 【3】封装 【4】构造函数constructor1》基础使用2》构造初始化列表 3》构造函数的调用方式 4》拷贝构造函数1>概......
  • C/C++指针的前世今生
    前言老早之前就想写这个内容了,打了草稿后闲置了两个月,因为其他事就没再动过这个东西了,今天翻草稿箱的时候发现了它,就把它完善出来,顺便我也学习学习。正文指针的前世今生前面先说一下,故事是随便瞎编的。在一个古老的计算机王国里,国王“硬件”统治着所有资源。他拥有广阔......
  • C++友元和运算符重载
    目录一.友元friend1.1概念1.2友元函数1.3友元类1.4友元成员函数二.运算符重载2.1概念2.2成员函数运算符重载2.3成员函数运算符重载2.4特殊运算符重载2.4.1赋值运算符重载2.4.2类型转换运算符重载2.5注意事项三、std::string字符串类(熟悉)一.友元......