Socket 初探

什么是Socket

在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
  socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
  我的理解就是Socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
  Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

Socket基础

首先看一个基于TCP的CS案例,客户端连接到服务器后,收到服务器发来的消息并打印,为步骤清晰,去掉了一些差错判断。

Server

#include <iostream>
#include <sys/socket.h> // int     socket(int, int, int);
#include <netinet/in.h> // IPPROTO_TCP
#include <arpa/inet.h> // in_addr_t	 inet_addr(const char *);
#include <unistd.h> //write and read

int setAddr(sockaddr_in * addr,short port,const char * ip){
    memset(addr, 0, sizeof(sockaddr_in));  //每个字节都用0填充
    addr->sin_family = AF_INET;  //使用IPv4地址
    addr->sin_addr.s_addr = inet_addr(ip);  //具体的IP地址
    addr->sin_port = htons(port);  //端口,使用htons转换为网络字节序
    return 0;
}

int main() {
    // 配置地址:ip port 协议
    sockaddr_in addr{};
    setAddr(&addr,1234,"127.0.0.1");

    // 创建socket
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  
    // 绑定socket和addr
    if(0!=bind(fd, (struct sockaddr *) &addr, sizeof(addr))){
        std::cout << "bind failed!" << std::endl;
    }

    // 监听指定地址端口
    if(0!=listen(fd, 20)){
        std::cout << "listen failed!" << std::endl;
    }

    //接收连接请求
    sockaddr_in clientAddr{};
    socklen_t clientAddrLen = sizeof(clientAddr);
    int clientFd = accept(fd, (struct sockaddr*)&clientAddr, &clientAddrLen);

    //向客户端发送数据
    char str[] = "Welcome!";
    write(clientFd, str, sizeof(str));

    //关闭套接字
    close(clientFd);
    close(fd);

    return 0;
}

Client

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int setAddr(sockaddr_in * addr,short port,const char * ip){
    memset(addr, 0, sizeof(sockaddr_in));  //每个字节都用0填充
    addr->sin_family = AF_INET;  //使用IPv4地址
    addr->sin_addr.s_addr = inet_addr(ip);  //具体的IP地址
    addr->sin_port = htons(port);  //端口
    return 0;
}

int main() {

    // 地址
    sockaddr_in addr{};

    setAddr(&addr,1234,"127.0.0.1");

    //1创建socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
  
    //2连接到socket
    if(0!=connect(fd,(struct sockaddr*)&addr,sizeof(addr) ) ){
        std::cout<<"connect failed!"<<std::endl;
    }

    //3读取服务器传回的数据
    char buffer[40];
    read(fd, buffer, sizeof(buffer)-1);
    printf("%s\n", buffer);

    //4关闭
    close(fd);
    return 0;
}

相关结构

typedef unsigned char           __uint8_t;
typedef __uint8_t               sa_family_t;
typedef __uint16_t              in_port_t;
typedef __uint32_t      				in_addr_t; 

struct sockaddr {
	__uint8_t       sa_len;
	sa_family_t     sa_family;//__uint8_t
	char            sa_data[14];
};

struct in_addr {
	in_addr_t s_addr;//__uint32_t
};

struct sockaddr_in {
	__uint8_t       sin_len;		//1byte
	sa_family_t     sin_family;	//1byte
	in_port_t       sin_port;		//2byte
	struct  in_addr sin_addr;		//4byte
	char            sin_zero[8];//8byte
};

相关函数

socket

/*
 * @file #include <sys/socket.h>
 * @brief create an endpoint for communication 
 * @param domain
 * 	PF_LOCAL(AF_UNIX)        		Host-internal protocols, formerly called PF_UNIX,
 * 	PF_UNIX(AF_UNIX)         		Host-internal protocols, deprecated, use PF_LOCAL,
 * 	PF_INET(AF_INET)         		Internet version 4 protocols,
 * 	PF_ROUTE(AF_ROUTE)        	Internal Routing protocol,
 * 	PF_KEY(pseudo_AF_KEY)       Internal key-management function,
 * 	PF_INET6(AF_INET6)        	Internet version 6 protocols,
 * 	PF_SYSTEM(AF_SYSTEM)       	System domain,
 * 	PF_NDRV(AF_NDRV)         		Raw access to network device,
 * 	PF_VSOCK(AF_VSOCK)        	VM Sockets protocols
 *
 * @param type
 * 	SOCK_STREAM			stream socket
 *	SOCK_DGRAM			datagram socket
 *	SOCK_RAW				raw-protocol interface
 *
 * @param protocol	usrally 0
 *	IPPROTO_TCP
 *	IPPROTO_UDP
 *	... more in netinet/in.h
 *
 * @return -1 if an error occurs, otherwise the return value is a descriptor referencing the socket.
 * 
 * ERRORS
     The socket() system call fails if:
     [EACCES]           Permission to create a socket of the specified type and/or protocol is denied.
     [EAFNOSUPPORT]     The specified address family is not supported.
     [EMFILE]           The per-process descriptor table is full.
     [ENFILE]           The system file table is full.
     [ENOBUFS]          Insufficient buffer space is available.  The socket
                        cannot be created until sufficient resources are freed.
     [ENOMEM]           Insufficient memory was available to fulfill the request.
     [EPROTONOSUPPORT]  The protocol type or the specified protocol is not supported within this domain.
     [EPROTOTYPE]       The socket type is not supported by the protocol.
     If a new protocol family is defined, the socreate process is free to return any desired error code.  
     The socket() system call will pass this error code along (even if it is undefined).
 */
int socket(int domain, int type, int protocol);

bind

/*
 * @file #include <sys/socket.h>
 * @brief bind a file descriptor with sockaddr
 * @param socket 			socket file descriptor
 * @param addr				point of addr struct
 * @param socklen_t 	size of addr
 */
int     bind(int socket, const struct sockaddr * addr, socklen_t) 

listen

/*
 * @file #include <sys/socket.h>
 * @brief bind a file descriptor with sockaddr
 * @param socket 				socket file descriptor
 * @param backlog				maximum length for the queue of pending connections
 * @return value 0 if successful; otherwise the value -1
 */
int listen(int socket, int backlog);

accept

/*
 * @file #include <sys/socket.h>
 * @brief bind a file descriptor with sockaddr
 * @param socket 									socket file descriptor
 * @param restrict_address				maximum length for the queue of pending connections
 * @param restrict_address_len		maximum length for the queue of pending connections
 * @return -1 on error,a non-negative integer that is a descriptor for the accepted socket if it succeeds.
 */
int accept(int socket, struct sockaddr *restrict_address, socklen_t *restrict_address_len);

read & write

/*
 * @file #include <unistd.h>
 * @brief read or write data from fd to buffer
 */
ssize_t	 read(int fd, void * buffer, size_t buffer_size) ;
ssize_t	 write(int fd, const void * buffer, size_t buffer_size) 

close

/*
 * @file #include <unistd.h>
 * @brief close a opened fd
 */
int  close(int fd);

recv & send

上述例子中使用了read和write函数对socket进行读取和写入数据,事实上,操作socket的数据有更好的方式,即使用recv函数和send函数。

send

#include <sys/socket.h>
/*
 * @brief send is used to transmit a message to another socket.
 * @param socket	socket file descriptor
 * @param buffer	message buffer
 * @param length  message bytes
 * @param flags  usrally 0。
 		The flags parameter may include one or more of the following: 
     #define MSG_OOB        0x1  // process out-of-band data 
     #define MSG_DONTROUTE  0x4  // bypass routing, use direct interface, usually used only by diagnostic or routing
     programs.
     
 * @return:
 			Upon successful completion, the number of bytes which were sent is returned. 
 			Otherwise, -1 is returned and the global variable errno is set to indicate the error.
*/
ssize_t send(int socket, const void *buffer, size_t length, int flags);

recv

#include <sys/socket.h>
/*
 * @brief recv receive messages from a socket, and may be used to receive data on
     a socket whether or not it is connection-oriented.
     
 * @param socket	socket file descriptor
 * @param buffer	message buffer
 * @param length  message bytes
 * @param flags  usrally 0。
 		The flags parameter may include one or more of the following: 
    	MSG_OOB        process out-of-band data
      MSG_PEEK       peek at incoming message
      MSG_WAITALL    wait for full request or error
     
 * @return:
 		 Return the number of bytes received, or -1 if an error occurred.
     For TCP sockets, the return value 0 means the peer has closed its half side of the connection.
*/
ssize_t recv(int socket, void *buffer, size_t length, int flags);

除此之外,还有

ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);

ssize_t sendto(int socket, const void *buffer, size_t length, int flags, 
               const struct sockaddr *dest_addr, socklen_t dest_len);
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, 
                 struct sockaddr *restrict address, socklen_t *restrict address_len);

关于read/write,recv/send,recvmsg/sendmsg,recvfrom/sendto的区别

  • read/write:通用文件描述符操作函数
  • send:发送消息,send只可用于基于连接的套接字,send 和 write唯一的不同点是标志的存在,当标志为0时,send等同于write。
  • sendto 和 sendmsg:既可用于无连接的套接字,也可用于基于连接的套接字;除了套接字设置为非阻塞模式,调用将会阻塞直到数据被发送完。
  • recv:读取消息,一般只用在面向连接的套接字。
  • recvfrom和recvmsg:可同时应用于面向连接的和无连接的套接字。

使用示例参考后文。

Example - Tcp Echo Server

基于有连接的网络通信,使用send和recv进行数据的收发

recv

char buffer[4096];
ssize_t bytes = recv(cfd, (void *)&buffer, 4096, 0);

send

char buffer[4096]{"test!"};
send(client_fd, &buffer, strlen(buffer), 0)

Server

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

int main(int , char **) {

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        std::cerr << "Socket creat failed!"<<std::endl;
        return -2;
    }

    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(1234);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (bind(fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr <<"bind error."<<std::endl;
        return -3;
    }
    if (listen(fd, 20) < 0) {
        std::cerr << "listen error."<<std::endl;
        return -4;
    }

    sockaddr_in client_addr{};
    socklen_t client_addr_size = sizeof(client_addr);
    int cfd = accept(fd, (sockaddr*)&client_addr, &client_addr_size);
    if (cfd < 0) {
        std::cerr << "accept error"<<std::endl;
        return -5;
    }
    close(fd);//关闭监听的socket


    char buffer[4096];
    ssize_t bytes;
    while (true) {
        bytes = recv(cfd, (void *)&buffer, 4096, 0);
        if (bytes == 0) {
            std::cout << "client disconnected."<<std::endl;
            break;
        }
        else if (bytes < 0) {
            std::cerr << "recv error."<<std::endl;
            break;
        }
        else {
            std::cout << "[client]" << std::string(buffer, 0, bytes) << std::endl;
            if (send(cfd, &buffer, bytes, 0) < 0) {
                std::cerr << "send error, exiting...\n";
                break;
            }
        }
    }
    close(cfd);

    return 0;
}

Client

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int , char **) {

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
        std::cerr << "Socket creat error."<<std::endl;
        return -2;
    }

    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(1234);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(fd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "connect error."<<std::endl;
        return -3;
    }

    char buf[4096];
    std::string inputTemp;
    while(true) {
        std::memset(buf, 0, 4096);
        std::cout << ">";
        std::getline(std::cin, inputTemp, '\n');
        std::strcpy(buf, inputTemp.c_str());

        if (strlen(buf)==0) {
            continue;
        }
        else if (strcmp(buf, "quit")==0) {
            break;
        }

        ssize_t bytes_send = send(fd, &buf, (size_t)strlen(buf), 0);
        if (bytes_send < 0) {
            std::cerr << "send error."<<std::endl;
            break;
        }

        ssize_t bytes_recv = recv(fd, &buf, 4096, 0);
        if (bytes_recv < 0) {
            std::cerr << "recv error."<<std::endl;
        }
        else if (bytes_recv == 0) {
            std::cout << "server closed.";
        }
        else {
            std::cout << "[server]" << std::string(buf, 0, bytes_recv) << std::endl;
        }
    }
    close(fd);
    return 0;
}

Example - Udp Echo Server

基于无连接的网络通信,使用sendto和recvfrom收发消息

sendto和recvfrom也可以支持面向连接的网络通信

recvfrom

ssize_t bytes_len = recvfrom(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &socklen);

sendto

ssize_t bytes_len = sendto(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, socklen);

Server

#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>

int main()
{
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(1234);

    int fd = socket(AF_INET, SOCK_DGRAM, 0);//SOCK_DGRAM=>udp
    if (fd < 0){
        return -1;
    }
    if (bind(fd, (const sockaddr *)&addr, sizeof(addr))<0){
        return -2;
    }
    char buf[1024];
    sockaddr_in client_addr{};
    socklen_t   socklen = sizeof(client_addr);

    while (true)
    {
        ssize_t bytes_len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &socklen);
        if (bytes_len < 0){
            std::cout<<"recv failed"<<std::endl;
            break;
        }
        else{
            std::cout<<"[client]"<<buf<<std::endl;
        }

        bytes_len = sendto(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, socklen);
        if (bytes_len < 0){
            std::cout<<"send failed"<<std::endl;
            break;
        }
    }
    close(fd);
    return 0;
}

Client

#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>

int main()
{
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(1234);

    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0){
        return -1;
    }

    ssize_t bytes_len ;
    char buf[1024] = "hello world!";
    bytes_len = sendto(fd, buf, strlen(buf), 0, (sockaddr*)&addr, sizeof(addr));
    if(bytes_len < 0){
        std::cout<<"send failed"<<std::endl;
        return -2;
    }
    bytes_len = recvfrom(fd, buf, strlen(buf), 0,nullptr, nullptr);
    if(bytes_len < 0){
        std::cout<<"recv failed"<<std::endl;
        return -3;
    }
    else{
        std::cout<<"[server]"<<buf<<std::endl;
    }
    close(fd);
    return 0;
}

Example - Sendmsg and Recvmsg

基于无连接的网络通信,使用sendto和recvfrom收发消息

sendto和recvfrom也可以支持面向连接的网络通信

首先回顾一下recvmsg和sendmsg的函数原型

struct iovec {
	void *   iov_base;      /* [XSI] Base address of I/O memory region */
	size_t   iov_len;       /* [XSI] Size of region iov_base points to */
};

struct msghdr {
	void            *msg_name;      /* [XSI] optional address */
	socklen_t       msg_namelen;    /* [XSI] size of address */
	struct          iovec *msg_iov; /* [XSI] scatter/gather array */
	int             msg_iovlen;     /* [XSI] # elements in msg_iov */
	void            *msg_control;   /* [XSI] ancillary data, see below */
	socklen_t       msg_controllen; /* [XSI] ancillary data buffer len */
	int             msg_flags;      /* [XSI] flags on received message */
};

ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
ssize_t recvmsg(int socket, struct msghdr *message, int flags);

Basic Usage

详细参考:https://blog.csdn.net/weixin_45309916/article/details/107905942

char buf[1024];
sockaddr_in client_addr{};

struct iovec msg_iov[1];
msg_iov[0].iov_base = buf;
msg_iov[0].iov_len = sizeof(buf);

struct msghdr msg{};
msg.msg_name = &client_addr;
msg.msg_namelen = sizeof(client_addr);
msg.msg_iov = msg_iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;

ssize_t bytes_len;

bytes_len = sendmsg(fd, &msg, 0);
bytes_len = recvmsg(fd, &msg, 0);

Server

#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
int main()
{
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(1234);

    int fd = socket(AF_INET, SOCK_DGRAM, 0);//SOCK_DGRAM=>udp
    if (fd < 0){
        return -1;
    }
    if (bind(fd, (const sockaddr *)&addr, sizeof(addr))<0){
        return -2;
    }
    char buf[1024];
    sockaddr_in client_addr{};

    iovec msg_iov[1];
    msg_iov[0].iov_base = buf;
    msg_iov[0].iov_len = sizeof(buf);

    msghdr msg{};
    msg.msg_name = &client_addr;
    msg.msg_namelen = sizeof(client_addr);
    msg.msg_iov = msg_iov;
    msg.msg_iovlen = 1;
    msg.msg_control = nullptr;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;

    while (true)
    {
        ssize_t bytes_len = recvmsg(fd,&msg,0);
        if(bytes_len < 0){
            std::cout<<"recv failed"<<std::endl;
            break;
        }
        else{
            std::cout<<"[client]"<<buf<<std::endl;
        }

        //change msg
        memset(buf,0,sizeof(buf));
        strcpy(buf,"Hi,Im server!");

        bytes_len = sendmsg(fd, &msg, 0);
        if (bytes_len < 0){
            std::cout<<"send failed"<<std::endl;
            break;
        }
    }
    close(fd);
    return 0;
}

Client

#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>

int main()
{
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(1234);

    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0){
        return -1;
    }

    ssize_t bytes_len ;
    char buf[1024] = "hello world!";

    iovec msg_iov{};
    msg_iov.iov_base = buf;
    msg_iov.iov_len = sizeof(buf);

    msghdr msg{};
    msg.msg_name = &addr;
    msg.msg_namelen = sizeof(addr);
    msg.msg_iov = &msg_iov;
    msg.msg_iovlen = 1;
    msg.msg_control = nullptr;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;

    bytes_len = sendmsg(fd, &msg, 0);
    if(bytes_len < 0){
        std::cout<<"send failed"<<std::endl;
        return -2;
    }

    bytes_len = recvmsg(fd,&msg,0);
    if(bytes_len < 0){
        std::cout<<"recv failed"<<std::endl;
        return -3;
    }
    else{
        std::cout<<"[server]"<<buf<<std::endl;
    }
    close(fd);
    return 0;
}

posted @ 2024-04-28 14:11  料峭春风吹酒醒  阅读(6)  评论(0编辑  收藏  举报