进行网络套接字编程之前,需要有计算机网络相关方面的知识。
Linux C语言 socket 编程 client 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define SERVER_IP "127.0.0.1" #define SERVER_PORT 12000 int main () { int sockfd = socket(AF_INET, SOCK_STREAM, 0 ); if (sockfd == -1 ) { perror("Socket creation failed" ); exit (EXIT_FAILURE); } struct sockaddr_in server_addr ; memset (&server_addr, 0 , sizeof (server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0 ) { perror("Invalid address or Address not supported" ); close(sockfd); exit (EXIT_FAILURE); } if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof (server_addr)) < 0 ) { perror("Connection failed" ); close(sockfd); exit (EXIT_FAILURE); } char *message = "Hello, server!" ; if (send(sockfd, message, strlen (message), 0 ) == -1 ) { perror("Send failed" ); close(sockfd); exit (EXIT_FAILURE); } printf ("Message sent to server: %s\n" , message); char buffer[1024 ]; int bytes_received = recv(sockfd, buffer, sizeof (buffer) - 1 , 0 ); if (bytes_received == -1 ) { perror("Recv failed" ); close(sockfd); exit (EXIT_FAILURE); } buffer[bytes_received] = '\0' ; printf ("Received from server: %s\n" , buffer); close(sockfd); return 0 ; }
编译:gcc -o client client.c
Terminal 下运行:./client
头文件 unistd.h & arpa/inet.h 这两个头文件 unistd.h
和 arpa/inet.h
都是 POSIX 标准 下的系统头文件,广泛用于 Unix/Linux 系统上的编程,尤其是网络编程和低级系统调用。
1. unistd.h
unistd.h
是一个与 Unix 系统调用相关的头文件,提供了大量与操作系统交互的功能,例如文件操作、进程管理、内存管理、IO 操作等。
常用功能:
文件操作 :
read()
: 读取文件描述符中的数据。
write()
: 向文件描述符写入数据。
close()
: 关闭文件描述符。
进程控制 :
fork()
: 创建一个新进程(分叉)。
exec()
: 替换当前进程的执行映像。
getpid()
: 获取当前进程的 PID。
getppid()
: 获取父进程的 PID。
文件描述符操作 :
dup()
, dup2()
: 复制文件描述符。
pipe()
: 创建管道。
时间管理 :
sleep()
: 使当前进程睡眠指定的秒数。
usleep()
: 使当前进程睡眠指定的微秒数。
系统信息 :
这些函数大多数涉及操作系统级别的基本功能,因此它们的效率高,广泛应用于各种系统编程中。
2. arpa/inet.h
arpa/inet.h
是与 Internet 地址处理 和 网络通信 相关的头文件,提供了对 IP 地址和端口号进行转换、网络字节序与主机字节序之间转换等功能。这些函数对于进行 网络编程 ,尤其是 TCP/IP 网络通信 非常重要。
常用功能:
IP 地址转换 :
inet_pton()
: 将 IP 地址从点分十进制字符串转换为网络字节序的二进制格式(用于 struct sockaddr_in
)。
inet_ntop()
: 将网络字节序的 IP 地址转换为点分十进制字符串。
主机字节序与网络字节序转换 :
htons()
: 将 16 位短整数从主机字节序转换为网络字节序(例如端口号)。
htonl()
: 将 32 位长整数从主机字节序转换为网络字节序(例如 IP 地址)。
ntohs()
: 将 16 位短整数从网络字节序转换为主机字节序。
ntohl()
: 将 32 位长整数从网络字节序转换为主机字节序。
这些函数通常用于 TCP/IP 套接字编程中,帮助程序处理地址、端口号的转换和网络字节序问题。
socket() 创建套接字 1 2 3 4 5 6 int sockfd = socket (AF_INET, SOCK_STREAM, 0 );if (sockfd == -1 ) { perror ("Socket creation failed" ); exit (EXIT_FAILURE); }
在 linux 系统上,可以使用 man
命令(Linux Manual Pages (man pages))来查看大部分 Linux 系统调用的文档,man 2 socket
:查看 socket()
函数的详细文档:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ man 2 socket ----------------------------- socket(2) System Calls Manual socket(2) NAME socket - create an endpoint for communication LIBRARY Standard C library (libc, -lc) SYNOPSIS #include <sys/socket.h> int socket(int domain, int type, int protocol); DESCRIPTION socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. The file descriptor returned by a suc‐ cessful call will be the lowest-numbered file descriptor not currently open for the process. The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. These families are defined in <sys/socket.h>. The formats currently understood by the Linux kernel include: Name Purpose Man page AF_UNIX Local communication unix(7) AF_LOCAL Synonym for AF_UNIX AF_INET IPv4 Internet protocols ip(7) AF_AX25 Amateur radio AX.25 protocol ax25(4) AF_IPX IPX - Novell protocols ... ... ... ... ... ...
socket()
是一个系统调用,用于创建一个新的套接字。套接字是进行网络通信的基础,通过它可以实现数据的发送和接收。socket()
函数会返回一个 套接字描述符 ,它是一个整数值,后续的网络操作(如连接、发送、接收等)都需要通过这个描述符进行。
socket() 函数第一个参数常用的有 AF_INET
(IPv4 地址族)和 AF_INET6
(IPv6 地址族)。第二个参数常用的有 SOCK_STREAM
(TCP)和 SOCK_DGRAM
(UDP)。第三个参数-通常指定套接字使用的协议。对于 SOCK_STREAM
类型,协议值通常设置为 0
,表示使用默认协议。在 IPv4 上,默认协议就是 TCP ,在 SOCK_DGRAM
类型下,默认协议是 UDP 。如果使用 AF_INET6
,则可以使用 IPPROTO_TCP
或 IPPROTO_UDP
来指定具体协议。
返回值:如果成功,则返回新套接字的文件描述符。如果失败,则返回 -1,并 适当设置 errno 。
sockaddr_in 结构体 struct sockaddr_in 是一个结构体,用来表示 IPv4 地址和端口信息。它定义在 <netinet/in.h> 头文件中,通常用于套接字编程中存储 IP 地址和端口信息。
1 2 3 4 5 6 7 8 struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8 ]; };
1 2 3 4 5 struct sockaddr_in server_addr;memset (&server_addr, 0 , sizeof (server_addr));server_addr.sin_family = AF_INET; server_addr.sin_port = htons (SERVER_PORT);
memset() 的作用是将指定的内存区域填充为特定的值。这里将 server_addr 结构体的所有字节设置为 0,这样可以确保结构体中的所有字段都初始化为 0,防止未初始化的字段造成意外行为。
inet_pton() inet_pton - convert IPv4 and IPv6 addresses from text to binary form(IP 地址转换成二进制形式)。
definition :
1 2 3 #include <arpa/inet.h> int inet_pton (int af, const char *src, void *dst) ;
返回值 :成功返回 1 。src IP 地址格式错误返回 0 。af 不合法 返回 -1 。
connect() 1 2 #include <sys/socket.h> int connect (int socket, const struct sockaddr *address, socklen_t address_len) ;
返回值 :成功返回0,其他返回 -1 。
send() 1 2 #include <sys/socket.h> ssize_t send (int socket, const void *buffer, size_t length, int flags) ;
返回值 :发送成功,返回发送数据的字节数;失败,返回 -1 。
recv() 1 2 #include <sys/socket.h> ssize_t recv (int socket, void *buffer, size_t length, int flags) ;
返回值 :接收数据成功返回接收数据的字节数;如果没有数据接收且对方已有序关闭连接返回 0;其他错误情况返回 -1 。
send() 和 recv() 中的 flags 参数 send() 和 recv() 的 flags 参数通常设置为 0,表示使用默认的行为模式。
默认行为(flags = 0
)的特点:
阻塞模式 :send()
和 recv()
默认都处于阻塞模式,意味着:
**send()
**:如果数据无法立刻发送(比如网络缓冲区已满),它会阻塞,直到有足够的空间可以发送数据。
**recv()
**:如果没有数据可读,它会阻塞,直到接收到至少一个字节的数据,或者对方关闭了连接。
按字节顺序发送/接收 :send()
会按顺序发送数据,recv()
会按顺序接收数据,返回已接收到的字节数。如果请求的数据量比接收到的少,recv()
会返回已接收到的字节数,剩余的数据需要在后续调用中接收。
正常的数据传输 :不进行任何特殊处理,如不启用带外数据、不使用非阻塞模式等。
其他 flags
标志:
MSG_OOB
:
发送 带外数据 (Out-of-Band Data)。通常用于传输紧急数据,但对于大多数应用程序来说,带外数据并不常用。
在 TCP 中,带外数据和普通数据并没有严格的区分,标记为带外数据的行为在很多情况下不起作用。
MSG_PEEK
:
使 recv()
函数或 recvfrom()
函数可以预读取数据,但数据并不会从缓冲区中被移除。即使数据被读取,下一次调用 recv()
或 recvfrom()
仍然会返回相同的数据。
这种标志在 recv()
上比较常用,但在 send()
上没有直接用途。
MSG_DONTROUTE
:
发送数据时, 不经过路由表 。这个标志告诉内核,不要尝试寻找默认路由,而是直接将数据发送到目标地址。通常用于开发中的调试或非常规的发送需求。
MSG_NOSIGNAL
:
当使用该标志时, send()
不会在发送过程中因信号的产生而导致 SIGPIPE
信号(即破损的管道错误)。通常与管道或套接字连接相关,如果尝试向已关闭的连接发送数据,默认会收到 SIGPIPE
信号,而通过 MSG_NOSIGNAL
可以避免这种情况。
MSG_WAITALL
:
用于 recv()
和 **recvfrom()
**,告知内核等待直到接收到指定长度的数据。它确保不会返回少于 length
字节的数据,除非连接关闭。
这对于一些需要完整接收数据的场景非常有用,尤其是在接收固定大小的数据时。
close() 释放之前创建的套接字的资源。
使用 C++ 面向对象编程思想封装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <iostream> #include <cstring> #include <unistd.h> #include <arpa/inet.h> class TcpClient {private : int sockfd; struct sockaddr_in server_addr; public : TcpClient (const std::string& server_ip, int server_port) { sockfd = socket (AF_INET, SOCK_STREAM, 0 ); if (sockfd == -1 ) { perror ("Socket creation failed" ); exit (EXIT_FAILURE); } memset (&server_addr, 0 , sizeof (server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons (server_port); if (inet_pton (AF_INET, server_ip.c_str (), &server_addr.sin_addr) <= 0 ) { perror ("Invalid address or Address not supported" ); close (sockfd); exit (EXIT_FAILURE); } } void connectToServer () { if (connect (sockfd, (struct sockaddr*)&server_addr, sizeof (server_addr)) < 0 ) { perror ("Connection failed" ); close (sockfd); exit (EXIT_FAILURE); } std::cout << "Connected to server\n" ; } void sendData (const std::string& message) { if (send (sockfd, message.c_str (), message.length (), 0 ) == -1 ) { perror ("Send failed" ); close (sockfd); exit (EXIT_FAILURE); } std::cout << "Message sent: " << message << std::endl; } void receiveData () { char buffer[1024 ]; int bytes_received = recv (sockfd, buffer, sizeof (buffer) - 1 , 0 ); if (bytes_received == -1 ) { perror ("Recv failed" ); close (sockfd); exit (EXIT_FAILURE); } buffer[bytes_received] = '\0' ; std::cout << "Received from server: " << buffer << std::endl; } void closeConnection () { close (sockfd); } ~TcpClient () { closeConnection (); } }; int main () { TcpClient client ("127.0.0.1" , 12000 ) ; client.connectToServer (); client.sendData ("Hello, server!" ); client.receiveData (); return 0 ; }
编译:g++ -o client client.cpp
Linux C语言 socket 编程 server 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define PORT 12000 #define BUFFER_SIZE 1024 int main () { int server_fd, new_socket; struct sockaddr_in address; int opt = 1 ; int addrlen = sizeof (address); char buffer[BUFFER_SIZE] = {0 }; const char *hello = "Hello from server" ; if ((server_fd = socket (AF_INET, SOCK_STREAM, 0 )) == 0 ) { perror ("socket failed" ); exit (EXIT_FAILURE); } if (setsockopt (server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt))) { perror ("setsockopt" ); exit (EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons (PORT); if (bind (server_fd, (struct sockaddr *)&address, sizeof (address)) < 0 ) { perror ("bind failed" ); exit (EXIT_FAILURE); } if (listen (server_fd, 3 ) < 0 ) { perror ("listen" ); exit (EXIT_FAILURE); } while (1 ) { printf ("等待连接...\n" ); if ((new_socket = accept (server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0 ) { perror ("accept" ); exit (EXIT_FAILURE); } read (new_socket, buffer, BUFFER_SIZE); printf ("收到消息: %s\n" , buffer); send (new_socket, hello, strlen (hello), 0 ); printf ("欢迎消息已发送\n" ); close (new_socket); } close (server_fd); return 0 ; }
编译:gcc -o server server.c
setsockopt() 1 2 3 #include <sys/socket.h> int setsockopt (int socket, int level, int option_name, const void *option_value, socklen_t option_len) ;
1 2 3 4 if (setsockopt (server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt))) { perror ("setsockopt" ); exit (EXIT_FAILURE); }
setsockopt() 函数,用于设置套接字的选项。是在创建一个服务器套接字时,配置套接字的一些行为或属性。SO_REUSEADDR
选项的设置允许在套接字关闭后,立即重用相同的地址(IP 和端口),而不必等到操作系统回收该端口。
**level
**:指定选项所在的协议层,通常是 SOL_SOCKET
,表示设置的是套接字级别的选项。
**optval
**:指向一个存储选项值的内存区域。这个值会根据不同的选项而变化,通常是一个整数或布尔值。
**optlen
**:optval
指向的内存区域的大小(字节数)。
int opt = 1;
opt 是 SO_REUSEADDR
选项的值通常是 1
(启用)或 0
(禁用)。sizeof(opt)
用来确定 opt
变量的大小。
setsockopt() 返回值:设置成功返回 0,否则返回 -1 。
address.sin_addr.s_addr = INADDR_ANY;
INADDR_ANY 是一个特殊的常量,值是 0.0.0.0,它代表任何可用的网络接口地址(所有的 IPv4 地址),通常用于绑定套接字到所有本地可用的网络接口,可以使得服务器程序更灵活地接受来自不同网络接口的请求。
bind() 1 2 3 #include <sys/socket.h> int bind (int socket, const struct sockaddr *address, socklen_t address_len) ;
返回值:绑定成功成功返回 0,否则返回 -1 。
listen() 1 2 #include <sys/socket.h> int listen (int socket, int backlog) ;
返回值:监听成功返回 0,否则返回 -1 。
accept() 1 2 3 #include <sys/socket.h> int accept (int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) ;
返回值:接受失败返回 -1,成功则返回接受套接字的非负文件描述。
read() 从套接字接收缓存中读取收到的数据。
1 2 #include <unistd.h> ssize_t read (int fildes, void *buf, size_t nbyte) ;
成功完成后,将返回一个非负整数,表示实际读取的字节数。否则,函数将返回 -1 并设置 errno 以指示错误。
server 端 C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <iostream> #include <cstring> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define PORT 12000 #define BUFFER_SIZE 1024 class Server {public : Server () { server_fd = socket (AF_INET, SOCK_STREAM, 0 ); if (server_fd < 0 ) { std::cerr << "Socket creation failed" << std::endl; exit (EXIT_FAILURE); } sockaddr_in address; int addrlen = sizeof (address); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons (PORT); if (bind (server_fd, (struct sockaddr *)&address, sizeof (address)) < 0 ) { std::cerr << "Bind failed" << std::endl; close (server_fd); exit (EXIT_FAILURE); } if (listen (server_fd, 3 ) < 0 ) { std::cerr << "Listen failed" << std::endl; close (server_fd); exit (EXIT_FAILURE); } std::cout << "Server is listening on port " << PORT << std::endl; } ~Server () { close (server_fd); } void acceptConnection () { sockaddr_in address; int addrlen = sizeof (address); int new_socket = accept (server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen); if (new_socket < 0 ) { std::cerr << "Accept failed" << std::endl; return ; } char buffer[BUFFER_SIZE]; int valread = read (new_socket, buffer, BUFFER_SIZE); std::cout << "Message from client: " << buffer << std::endl; const char *response = "Hello from server" ; send (new_socket, response, strlen (response), 0 ); std::cout << "Response sent to client" << std::endl; close (new_socket); } private : int server_fd; }; int main () { Server server; while (true ) { server.acceptConnection (); } return 0 ; }
编译:g++ -o server server.cpp
运行
Linux socket 编程在线英文文档 点击跳转