本文主要讲解Socket网络编程。
首先介绍socket,包括TCP和UDP通信过程;然后介绍常用的函数;最后编写client-server例子,并进行测试。
文章目录
- Socket介绍
 - TCP通信过程
 - 服务器端通信过程:
 - 客户端通信过程:
 
- UDP通信过程
 - 服务器端通信过程:
 - 客户端通信过程:
 
- 常用函数介绍
 - socket
 - 函数原型
 - 参数介绍
 
- bind
 - 函数原型
 - 参数介绍
 
- listen
 - 函数原型
 - 参数介绍
 
- connect
 - 函数原型
 - 参数介绍
 
- accept
 - 函数原型
 - 参数介绍
 
- close
 - 函数原型
 - 参数介绍
 
- recv
 - 函数原型
 - 参数介绍
 
- send
 - 函数原型
 - 参数介绍
 
- recvmsg
 - 函数原型
 - 参数介绍
 
- sendmsg
 - 函数原型
 - 参数介绍
 - msghdr
 - flag
 
- setsockopt
 - 函数原型
 - 参数介绍
 
- demo
 - 服务端代码
 - 客户端代码
 - 测试结果
 
Socket介绍
C 语言中的 socket 编程是一种用于在网络上进行通信的编程接口。通过 socket,程序可以在不同的计算机之间进行数据交换,实现网络通信的功能。
TCP通信过程

服务器端通信过程:
-  
创建 Socket:使用 socket() 函数创建一个套接字,指定地址族为 AF_INET,类型为 SOCK_STREAM(表示流式套接字),协议为 IPPROTO_TCP。这一步通常会得到一个套接字描述符。
 -  
绑定 Socket:调用 bind() 函数将套接字与服务器所在主机的 IP 地址和端口号绑定。这样服务器就可以监听来自该端口的连接请求。
 -  
监听连接请求:使用 listen() 函数开始监听客户端的连接请求,并设置最大连接数。
 -  
接受连接:当有客户端发起连接请求时,使用 accept() 函数接受连接,返回一个新的套接字描述符用于与该客户端进行通信。
 -  
数据通信:通过新的套接字描述符,使用 send() 和 recv() 函数发送和接收数据。通常在一个循环中进行数据交换,直到通信结束。
 -  
关闭连接:通信结束后,关闭新的套接字描述符,释放资源。
 
客户端通信过程:
-  
创建 Socket:同样使用 socket() 函数创建套接字,地址族为 AF_INET,类型为 SOCK_STREAM,协议为 IPPROTO_TCP。
 -  
连接服务器:使用 connect() 函数向服务器发起连接请求,指定服务器的 IP 地址和端口号。
 -  
数据通信:连接建立后,使用 send() 和 recv() 函数发送和接收数据,通常也是在一个循环中进行数据交换,直到通信结束。
 -  
关闭连接:通信结束后,关闭套接字描述符,释放资源。
 
UDP通信过程

服务器端通信过程:
-  
创建 Socket:调用socket()函数创建一个套接字,指定地址族为AF_INET,类型为SOCK_DGRAM(数据报套接字),协议为IPPROTO_UDP。这一步通常会得到一个套接字描述符。
 -  
绑定 Socket:使用bind()函数将套接字与服务器所在主机的IP地址和端口号绑定。这样服务器就可以监听来自该端口的数据报。
 -  
数据通信:通过绑定的套接字描述符,使用sendto()和recvfrom()函数发送和接收数据报。UDP是无连接的协议,因此每次发送数据时都需要指定目标的IP地址和端口号。
 -  
关闭连接:通信结束后,关闭套接字描述符,释放资源。
 
客户端通信过程:
-  
创建 Socket:同样使用socket()函数创建套接字,地址族为AF_INET,类型为SOCK_DGRAM,协议为IPPROTO_UDP。
 -  
数据通信:通过创建的套接字描述符,使用sendto()和recvfrom()函数发送和接收数据报。UDP是无连接的协议,因此每次发送数据时都需要指定目标的IP地址和端口号。
 -  
关闭连接:通信结束后,关闭套接字描述符,释放资源。
 
常用函数介绍
socket
函数原型
创建一个新的套接字,返回一个 int 类型的套接字文件描述符,用于后续的网络连接操作。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
 
参数介绍
- domain:指定 Socket AF(Address Family,地址族)可选:AF_INET(IPv4)或AF_INET6(IPv6)
 
- type:指定数据传输方式,可选: 
  
- SOCK_STREAM(面向连接的 TCP)
 - SOCK_DGRAM(无连接的 UDP)
 - SOCK_RAW(原始 IP 数据包)
 
 
- protocol:指定具体的传输层协议,可选: 
  
- IPPROTO_TCP
 - IPPTOTO_UDP
 
 - 函数返回值: 
  
- 成功:返回 Socket fd。
 - 失败:返回 -1。
 
 
bind
函数原型
将套接字与一个本地 IP:Port 绑定。通常用于服务端,以便在本地监听网络连接
#include <sys/socket.h>
int bind(int socket, struct sockaddr *addr, socklen_t addrlen); 
 
参数介绍
- socket:指定 Server socket 文件描述符。
 - addr:指定 Server sockaddr 结构体变量,指针类型。
 - addrlen:指定 addr 变量的大小,使用 sizeof() 计算得出。
 - 函数返回值: 
  
- 成功:返回 0。
 - 失败:返回 -1。
 
 
listen
函数原型
开始监听来自远程主机的连接请求。通常用于服务器端,在套接字上等待来自客户端的连接请求。
#include <sys/socket.h>
int listen(int socket, int backlog);
 
参数介绍
- socket:指定需要进入监听状态的 Server socket 文件描述符。
 - backlog:指定请求队列的最大长度,当队列满了之后,就不再接收请求。
 - 函数返回值: 
  
- 成功:返回 0。
 - 失败:返回 -1。
 
 
connect
函数原型
建立与远程主机的连接。通常用于客户端,以便连接到远程服务器
#include <sys/socket.h>
int connect(int socket, struct sockaddr *serv_addr, socklen_t addrlen);  
 
参数介绍
- socket:指定 Client socket 文件描述符。
 - serv_addr:指定 Server sockaddr 结构体变量,指针类型。
 - addrlen:指定 addr 变量的大小,使用 sizeof() 计算得出。
 - 函数返回值: 
  
- 成功:返回 0。
 - 失败:返回 -1。
 
 
accept
函数原型
接受一个连接请求,返回一个新的、表示客户端的 Socket 文件描述符,作为服务端和客户端之间发送与接收操作的句柄。通常用于服务器端,用于接收客户端的连接请求。
#include <sys/socket.h>
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
 
参数介绍
- socket:指定 Server socket 文件描述符。
 - addr:指定 Client sockaddr 结构体变量,指针类型。
 - addrlen:指定 addr 变量的大小,使用 sizeof() 计算得出。
 - 函数返回值: 
  
- 成功:返回 Client socket fd。
 - 失败:返回 -1。
 
 
close
函数原型
关闭套接字连接。
#include <unistd.h>
int close(int fildes);
 
参数介绍
- fd:指定要关闭的 Socket 的文件描述符。
 - 函数返回值: 
  
- 成功:返回0。
 - 失败:返回 -1。
 
 
recv
函数原型
从套接字接收数据
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 
参数介绍
- sockfd:指定要接收 TCP 数据的 Socket 文件描述符。
 - buf:指定接收数据缓冲区的入口地址。
 - len:指定要接收的数据的 Byte 数目。
 - flags:指定接收数据时的选项,常设为 0。
 - 函数返回值: 
  
- 成功:返回接收的字节数。
 - 失败:返回 -1。
 
 
send
函数原型
向套接字发送数据。
 #include <sys/socket.h>
 
 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 
参数介绍
- sockfd:指定要发送 TCP 数据的 Socket 文件描述符。
 - buf:指定发送数据缓冲区入的口地址。
 - len:指定要发送数据的 Byte 数目。
 - flags:指定发送数据时的选项,常设为 0。
 - 函数返回值: 
  
- 成功:返回发送的字节数。
 - 失败:返回 -1。
 
 
recvmsg
函数原型
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
 
参数介绍
- sock:指定要接收 TCP 或 UDP 数据的 Socket 文件描述符。
 - msg:指示将接收的数据存储到 msghdr 结构体中。
 - flags:支持函数的行为,可选 0 或者 MSG_DONTWAIT 等标志位。
 - 函数返回值: 
  
- 成功:返回接收的字节数。
 - 失败:返回 -1。
 
 
sendmsg
函数原型
#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
 
参数介绍
- sock:指定要发送 TCP 或 UDP 数据的 Socket 文件描述符。
 - msg:指示 msghdr 结构体中包含了要发送的数据、数据长度等信息。
 - flags:支持函数的行为,可选 0 或者 MSG_DONTWAIT 等标志位。
 - 函数返回值: 
  
- 成功:返回发送的字节数。
 - 失败:返回 -1。
 
 
msghdr
struct msghdr {
    /* 指定接收或发送数据的对端地址,可以为 NULL 或 0,表示不需要使用对端地址。*/
    void         *msg_name;       /* optional address */
    socklen_t     msg_namelen;    /* size of address */
    /* 指定接收或发送数据的缓冲区和缓冲区大小,可以使用多个缓冲区同时接收或发送数据。*/
    struct iovec *msg_iov;        /* scatter/gather array */
    size_t        msg_iovlen;     /* # elements in msg_iov */
 /* 指定一些附加的控制信息,可以为 NULL 或 0。*/
    void         *msg_control;    /* ancillary data, see below */
    size_t        msg_controllen; /* ancillary data buffer len */
 /* 指定函数的行为,例如是否需要接收带外数据等。*/
    int           msg_flags;      /* flags on received message */
};
 
flag
- MSG_PEEK:允许从接收队列中查看数据而不将其删除。这意味着,如果接收队列中有数据,recv() 函数将返回数据的一个副本,但是该数据仍将留在接收队列中。这对于查看接收队列中的数据而不实际处理它们非常有用。此外,使用 MSG_PEEK 选项,我们可以检查套接字缓冲区中是否有足够的数据可供读取,以便稍后调用 recv() 函数。
 - MSG_WAITALL:如果套接字缓冲区中没有足够的数据,则 recv() 函数将一直等待,直到收到请求的数据量。
 - MSG_DONTWAIT:指定此标志后,recv() 函数将立即返回,即使没有收到数据也不会阻塞。如果没有数据可用,则 recv() 将返回 -1,并将 errno 设置为 EAGAIN 或 EWOULDBLOCK。
 - MSG_OOB:用于处理带外数据,即紧急数据。带外数据不遵循正常的传输控制协议(如 TCP),可以使用此标志将其标记为紧急数据并将其与其他数据分开处理。
 - MSG_TRUNC:如果接收缓冲区中的数据比接收缓冲区长度长,则截断数据并返回。
 - MSG_CTRUNC:如果接收缓冲区中的控制消息(例如带外数据或错误消息)比接收缓冲区长度长,则截断消息并返回。
 
setsockopt
函数原型
设置socket选项值
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
 
参数介绍
- sockfd:指定 socket fd。
 - level:指定选项的协议层,可选 SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP 等。
 - optname:指定要设置的选项名。 
  
- SO_REUSEADDR:int 类型,表示重用 IP 地址。
 - SO_KEEPALIVE:int 类型,用于启用/禁用 Keepalive(保持连接)功能。
 - SO_LINGER:struct linger 类型,用于指定关闭套接字时的行为。
 - TCP_NODELAY:int 类型,用于禁用 Nagle 算法,从而实现数据的实时传输。
 
 - optval:指定存放选项值的缓冲区入口。
 - optlen:指定选项值缓冲区的长度。
 - 函数返回值: 
  
- 成功:0。
 - 失败:-1,并设置了 errno 错误码。
 
 
demo
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8888
#define MAXLINE 1024
int main() {
    int sockfd, connfd;
    char buffer[MAXLINE];
    struct sockaddr_in servaddr, cli;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 创建TCP套接字
    if (sockfd == -1) {
        printf("socket creation failed");
        return EXIT_FAILURE;
    }
    // 设置服务器地址结构
    servaddr.sin_family = AF_INET; // IPv4
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);
    // 绑定服务器套接字
    if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0) {
        printf("socket bind failed");
        return EXIT_FAILURE;
    }
    // 监听连接
    if (listen(sockfd, 5) != 0) {
        printf("listen failed");
        return EXIT_FAILURE;
    }
    socklen_t len = sizeof(cli);
    // 接受连接
    connfd = accept(sockfd, (struct sockaddr*)&cli, &len);
    if (connfd < 0) {
        printf("server accept failed");
        return EXIT_FAILURE;
    }
    // 读取客户端消息
    ssize_t num = recv(connfd, buffer, sizeof(buffer),0);
    if (num < 0){
        printf("read data from client fail, num=%ld\n", num);
        return EXIT_FAILURE;
    }
    printf("Client message: %s, size: %ld\n", buffer, num);
    char * data = "hello world";
    // 发送消息给客户端
    num = send(connfd, data, strlen(data),0);
    if (num < 0){
        printf("write data to client fail, num=%ld\n", num);
        return EXIT_FAILURE;
    }
    // 关闭连接
    close(sockfd);
    return 0;
}
 
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8888
#define MAXLINE 1024
int main() {
    int sockfd;
    char buffer[MAXLINE];
    struct sockaddr_in servaddr;
    // 创建TCP套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        printf("socket creation failed\n");
        return EXIT_FAILURE;
    }
    // 设置服务器地址结构
    servaddr.sin_family = AF_INET; // IPv4
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 连接服务器
    if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0 ) {
        printf("connection with server failed\n");
        return EXIT_FAILURE;
    }
    char *data = "client send data";
    // 发送消息给服务器
    ssize_t num = send(sockfd, data, strlen(data),0);
    if (num < 0 )
    {
        printf("write data to server fail\n");
        return EXIT_FAILURE;
    }
    // 读取服务器响应
    num = recv(sockfd, buffer, sizeof(buffer),0);
    if (num < 0 )
    {
        printf("read data from client fail\n");
        return EXIT_FAILURE;
    }
    printf("Server response: %s\n", buffer);
    // 关闭连接
    close(sockfd);
    return 0;
}
 
测试结果
编译和运行服务端:
 编译和运行客户端:
 



















