一、Linux C/C++ 网路socket基础代码

本文详细介绍了服务端编程中使用socket函数创建、绑定、监听、接受连接以及数据传输的过程,并讨论了服务端主动或被动关闭连接时TIME_WAIT和CLOSE_WAIT状态的出现。
摘要由CSDN通过智能技术生成

需要用到的函数

1、int socket (int __domain, int __type, int __protocol);

1. __domain为地址族(Address Family),IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF_INET 表示IPv4 地址。
2. __type为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)。
3. __protocol表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议,正常情况下该参数为0,前提是参数1和2能确定唯一的协议信息。
4. int返回值是文件描述符,是从3开始的。0,1,2分别是该进程内部使用的stdin(输入),stdout(输出),stderr(错误)。

该函数的详细介绍:https://blog.csdn.net/qq_53511990/article/details/130659630

2、 int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

1. __fd为 socket 文件描述符。
2. __addr为 sockaddr 结构体变量的指针。
3. __len为 addr 变量的大小,可由 sizeof() 计算得出。

使用的struct sockaddr_in结构体:
	struct sockaddr_in{
		sa_family_t sin_family; //地址族(Address Family),也就是地址类型
		uint16_t sin_port; //16位的端口号
		struct in_addr sin_addr; //32位IP地址
		char sin_zero[8]; //不使用,一般用0填充
	};

	struct in_addr{
		in_addr_t s_addr; //32位的IP地址
	};


3、int listen (int __fd, int __n);

socket进入监听状态,此时就可以监听并接受客户端的连接(是由tcp/ip协议栈来监听的)。
__fd为需要进入监听状态的套接字,__n为请求队列的最大长度。


4、int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t * __restrict __addr_len);

1. __fd是服务器端的套接字
2. __addr用来接收保存连接上的客户端的IP地址和端口号
3. __addr_len是__addr长度的变量的指针
4. 返回值int是客户端文件描述符,每连接一个socket,返回的值是依次递增的(4,5,6....),需要注意的是,如果有客户端关闭连接了,再次有新的客户端连接进来时,accept返回的是没有使用的最小的描述符,
   比如现在有4,5,6客户端建立了连接,4和5客户端断开了,再次有新的客户端建立连接后,accept会给分配4描述符,这点内核还是很人性化的。

当套接字处于监听状态后,可以通过 accept() 函数来接收客户端请求。
accept() 返回一个新的套接字来和客户端通信,**后面和客户端通信时,要使用这个新生成的套接字。**
注意:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇
到 accept()。
accept() 会阻塞程序执行(后面代码不能被执行),直到有新的客户端连接到来。


5、ssize_t send (int __fd, const void * __buf, size_t __n, int __flags);

1. __fd为要发送数据的套接字
2. buf 为要发送的数据的缓冲区地址
3. __n为要发送的数据的字节数
4. flags 为发送数据时的选项


6、ssize_t recv (int __fd, void * __buf, size_t __n, int __flags);

  • __fd
    • 客户端或服务端的socket,每个客户端对应唯一的socket
  • buf
    • 客户端消息的存储空间,也就是个字符数组
    • 一般为1500字节
      • 网络传输的最大单元,1500字节,也就是客户端发过来的数据,一次最大就是1500字节,这是协议规定,这个数值也是根据很多情况,总结出来的最优值
      • 所以客户端最多一组来1500字节,咱们这头1500读一次,就够了。

  • __n
    • 想要读取到字节个数

  • flags
    • 一般设置为0

    • MSG_PEEK:窥视传入的数据。 数据被复制到缓冲区中,但不会从输入队列中删除。

    • MSG_OOB:处理带外(OOB)数据。

    • MSG_WAITALL:

      仅当发生以下事件之一时,接收请求才会完成:

      • 调用方提供的缓冲区已完全满。
      • 连接已关闭。
      • 该请求已被取消或发生错误。

  • 返回值
    • 读出来的字节大小

    • 客户端下线,返回0
      • 释放客户端socket

    • 执行失败,返回-1
      • 重启
      • 等待
      • 不用理会

7、int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

 客户端连接服务器使用,与bind类似。


服务端代码

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

using namespace std;

void *client_thread(void *arg) {
    int clientfd = *((int*)arg);

    while (1) {
        char buf[128] = {0};
        //接收数据, 阻塞函数,直到收到客户端发来的数据,代码继续往下执行
        int count = recv(clientfd, buf, sizeof(buf), 0);
        if (count <= 0) {
            //返回值为0,代表客户端下线,为-1代表执行出错
            perror("recv");
            break;
        }
        printf("clientfd: %d, count: %d, buffer: %s\n", clientfd, count, buf);

        //发送给客户端
        send(clientfd, buf, count, 0);
    }
    
    //释放客户端socket
    close(clientfd);

    return NULL;
}

int main() {
    //1.int socket (int __domain, int __type, int __protocol);
    //(1)__domain 为地址族(Address Family),IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址。
    //(2)type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)。
    //(3)protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议,正常情况下该参数为0,前提是参数1和2能确定唯一的协议信息。
    //(4)返回值是文件描述符
    int socketfd = socket(AF_INET, SOCK_STREAM, 0);
    if (socketfd == -1) {
        int err = errno; //获取错误代码
        perror("socket创建失败!");

        return -1;
    }

    //地址
    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(sockaddr_in));
    serverAddr.sin_family = AF_INET; //地址族(Address Family)
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); ///32位IP地址(htonl:将32位的主机字节序转换成网络字节序)
    serverAddr.sin_port = htons(2048); //16位的端口号(htonl:将16位的主机字节序转换成网络字节序)

    //绑定
    if (-1 == bind(socketfd, (sockaddr*)&serverAddr, sizeof(sockaddr))) {
        //perror的工作原理:当一个系统调用或库函数发生错误时,
        //通常会将全局变量errno设置为一个特定的错误码。
        //perror函数读取errno的值,并根据这个值生成相应的错误描述。
        //然后,将错误描述与传入的字符串参数拼接,并输出到标准错误流。
        perror("bind");
        return -1;
    }

     //进入监听状态
    if (-1 == listen(socketfd, 10)) {
        perror("listen");
        return -1;
    }

    //通过创建线程,支持多个客户端连接,一个客户端连接创建一个线程
    sockaddr_in clientAddr;
    socklen_t len = sizeof(clientAddr);
    while (1) {
        memset(&clientAddr, 0, sizeof(clientAddr));

        //接收客户端的连接,阻塞函数,直到有客户端连接上,代码继续往下执行
        int clientfd = accept(socketfd, (sockaddr*)&clientAddr, &len);
        if (-1 == clientfd) {
            perror("accept");
            return -1;
        }

		//来一个连接,创建一个线程。缺点:大量客户端连接时,需要大量的线程,无法满足高并发,出现C10K的问题
        pthread_t thid;
		pthread_create(&thid, NULL, client_thread, &clientfd);
    }

	close(socketfd);
	
    getchar();

    return 0;
}


问题总结

  • 当服务端代码当掉或者调用close()函数主动关闭socket连接时,通过 netstat -anop | grep 端口号 命令来查看该端口的连接状态,会出现TIME_WAIT的状态,这是因为服务器主动关闭连接引起的问题。

  • 当客户端关闭连接时,在服务端通过 netstat -anop | grep 端口号 命令来查看该端口的连接状态,会出现CLOSE_WAIT的状态,这是因为服务器还没close()该socket引起的

  • 补充: linux下的netstat 命令可以查看当前端口列表及指定端口的连接状态等;详情请见https://www.cnblogs.com/zjdxr-up/p/16428925.html

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值