Linux网络编程基础API-学习笔记
主机字节序和网络字节序
大端字节序:网络字节序
利用union公用体查看自己电脑的字节序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <stdio.h> void byteorder() { union { short value; char union_bytes[ sizeof( short ) ]; } test; test.value = 0x0102; if ( ( test.union_bytes[ 0 ] == 1 ) && ( test.union_bytes[ 1 ] == 2 ) ) { printf( "big endian\n" ); } else if ( ( test.union_bytes[ 0 ] == 2 ) && ( test.union_bytes[ 1 ] == 1 ) ) { printf( "little endian\n" ); } else { printf( "unknown...\n" ); } }
|

现代的PC机大多采用小端序:主机字节序
socket
linux:所有的东西都是文件。socket:可读,可写可控制,可关闭的文件描述符。
创建socket:
1 2 3
| #include <sys/types.h> #include<sys/socket.h> int socket(int domain,int type,int protocol);
|
1 2 3 4 5 6
|
int sock = socket( PF_INET, SOCK_STREAM, 0 );
|
bind
将一个socket和socket地址绑定称为给socket命名。
1 2 3
| #include <sys/types.h> #include<sys/socket.h> int bind(int sockfd,const struct sockaddr * myaddr,socklen_taddrlen)
|
绑定成功返回0,失败返回-1.
监听socket
创建一个监听队列存放待处理的客户连接
1 2
| #include<sys/socket.h> int listen(int sockfd,int backlog)
|
backlog:内核监听队列的最大长度,
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
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <string.h>
static bool stop = false; static void handle_term( int sig ) { stop = true; }
int main( int argc, char* argv[] ) { signal( SIGTERM, handle_term ); if( argc <= 3 ) { printf( "usage: %s ip_address port_number backlog\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); int backlog = atoi( argv[3] ); int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 );
struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 );
ret = listen( sock, backlog ); assert( ret != -1 );
while ( ! stop ) { sleep( 1 ); }
close( sock ); return 0; }
|
运行程序:./a.out 172.21.245.193 255 5
报错解决:
1 2
| : Cannot assign requested address a.out: testlisten.cpp:48: int main(int, char**): Assertion `ret != -1' failed.
|
- 第一步,先查一下自己ubuntu的IP地址 hq123@ubuntu:~/a_test$ ifconfig
server.sin_addr.s_addr = inet_addr("xxx"); // 服务器的 IP 地址
- 第二步 ,bind 绑定的IP地址要和自己电脑(ubuntu)的IP地址一
运行上述程序,在客户端远程连接服务器:
telnet 服务器公网IP 255
此时端口状态:
1 2 3 4
| ❯ lsof -i :255 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME a.out 179304 root 3u IPv4 1430112 0t0 TCP localhost:255 (LISTEN)
|
查看端口状态:netstat -nt |grep 255
1 2
| ❯ netstat -nt |grep 255 tcp 0 0 172.21.245.193:255 111.199.69.168:2733 ESTABLISHED
|
释放端口:sudo fuser -k -n tcp 255
通过实验可以发现,监听队列的长度超过backlog的时候服务器不受理新的客户连接
服务器开启防火墙端口
查看防火墙状态
1
| systemctl status firewalld.service
|
开启防火墙
1
| systemctl start firewall.service
|
重启防火墙
关闭防火墙
1
| systemctl stop firewalld.service
|
开放端口(更改后要重启防火墙)
1
| firewall-cmd --zone=public --add-port=255/tcp --permanent
|
接受连接
1 2 3
| #include <sys/types.h> #include<sys/socket.h> int acept(int sockfd,struct sockaddr* addr,socklen_t *addrlen);
|
addr:被接受的远端socket地址,addrlen地址长度
接受一个异常的连接:
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
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h>
int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] );
struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 );
ret = listen( sock, 5 ); assert( ret != -1 );
struct sockaddr_in client; socklen_t client_addrlength = sizeof( client ); int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); } else { char remote[INET_ADDRSTRLEN ]; printf( "connected with ip: %s and port: %d\n", inet_ntop( AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN ), ntohs( client.sin_port ) ); close( connfd ); }
close( sock ); return 0; }
|
address.sin_port
字段表示套接字绑定的端口号。具体来说,sin_port
是
struct sockaddr_in
结构体的一个字段,它存储了套接字的端口信息。和之前不一样,这次是直接连接了
客户端连接
服务器连接
accept对客户端网络的断开毫不知情。
TCP数据读写
对文件的读写同样适用于socket,其中recv读取sockfd上的数据,send向sockfd写数据
客户端:
send( sockfd, normal_data, strlen( normal_data ), 0 );
使用 send
函数,第一个参数是套接字描述符,第二个参数是指向要发送数据的指针,第三个参数是要发送的数据的长度,最后一个参数是标志,此处标志为
0,表示普通数据
send(sockfd, oob_data, strlen(oob_data), MSG_OOB);
发送带外数据到服务器,使用 send
函数,最后一个参数设置为
MSG_OOB
,表示带外数据,用于发送或接受紧急数据
整体程序:
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
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h>
int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] );
struct sockaddr_in server_address; bzero( &server_address, sizeof( server_address ) ); server_address.sin_family = AF_INET; inet_pton( AF_INET, ip, &server_address.sin_addr ); server_address.sin_port = htons( port );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 ); assert( sockfd >= 0 ); if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 ) { printf( "connection failed\n" ); } else { printf( "send oob data out\n" ); const char* oob_data = "abc"; const char* normal_data = "123"; send( sockfd, normal_data, strlen( normal_data ), 0 ); send( sockfd, oob_data, strlen( oob_data ), MSG_OOB ); send( sockfd, normal_data, strlen( normal_data ), 0 ); }
close( sockfd ); return 0; }
|
服务器接收:
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
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h>
#define BUF_SIZE 1024
int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] );
struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 );
ret = listen( sock, 5 ); assert( ret != -1 );
struct sockaddr_in client; socklen_t client_addrlength = sizeof( client ); int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); } else { char buffer[ BUF_SIZE ];
memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, 0 ); printf( "got %d bytes of normal data '%s'\n", ret, buffer );
memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB ); printf( "got %d bytes of oob data '%s'\n", ret, buffer );
memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, 0 ); printf( "got %d bytes of normal data '%s'\n", ret, buffer );
close( connfd ); }
close( sock ); return 0; }
|
memset(buffer, '\0', BUF_SIZE);
: 将
buffer
数组初始化为零,以确保不会出现垃圾数据。
ret = recv(connfd, buffer, BUF_SIZE - 1, 0);
:
使用 recv
函数接收普通数据,connfd
是连接的套接字描述符,buffer
是接收数据的缓冲区,BUF_SIZE - 1
是要接收的最大字节数,0
表示没有特殊标志。
客户端
服务端
UDP数据读写
和tcp不同,由于udp是无连接的,因此因此每次都要指定发送端的socket地址(ip+端口)
重用本地地址
SO_REUSEADDR
是套接字选项的一种,用于控制套接字地址的重用。
- 当
SO_REUSEADDR
设置为非零值时,表示允许重用处于
TIME_WAIT 状态的套接字地址。
- 这对于快速重新启动服务器程序而无需等待 TIME_WAIT 超时非常有用。
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
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h>
int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] );
int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 ); int reuse = 1; setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );
struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port ); int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 );
ret = listen( sock, 5 ); assert( ret != -1 );
struct sockaddr_in client; socklen_t client_addrlength = sizeof( client ); int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); } else { char remote[INET_ADDRSTRLEN ]; printf( "connected with ip: %s and port: %d\n", inet_ntop( AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN ), ntohs( client.sin_port ) ); close( connfd ); }
close( sock ); return 0; }
|
修改TCP缓冲区
客户端修改发送缓冲区:
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
| #include <sys/socket.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h>
#define BUFFER_SIZE 512
int main( int argc, char* argv[] ) { if( argc <= 3 ) { printf( "usage: %s ip_address port_number send_bufer_size\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] );
struct sockaddr_in server_address; bzero( &server_address, sizeof( server_address ) ); server_address.sin_family = AF_INET; inet_pton( AF_INET, ip, &server_address.sin_addr ); server_address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 );
int sendbuf = atoi( argv[3] ); int len = sizeof( sendbuf ); setsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof( sendbuf ) );
getsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, ( socklen_t* )&len );
printf( "the tcp send buffer size after setting is %d\n", sendbuf );
if ( connect( sock, ( struct sockaddr* )&server_address, sizeof( server_address ) ) != -1 ) { char buffer[ BUFFER_SIZE ]; memset( buffer, 'a', BUFFER_SIZE ); send( sock, buffer, BUFFER_SIZE, 0 ); }
close( sock ); return 0; }
|
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 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
| #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h>
#define BUFFER_SIZE 1024
int main( int argc, char* argv[] ) { if( argc <= 3 ) { printf( "usage: %s ip_address port_number receive_buffer_size\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] );
struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 ); int recvbuf = atoi( argv[3] ); int len = sizeof( recvbuf ); setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof( recvbuf ) ); getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, ( socklen_t* )&len ); printf( "the receive buffer size after settting is %d\n", recvbuf );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 );
ret = listen( sock, 5 ); assert( ret != -1 );
struct sockaddr_in client; socklen_t client_addrlength = sizeof( client ); int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); } else { char buffer[ BUFFER_SIZE ]; memset( buffer, '\0', BUFFER_SIZE ); while( recv( connfd, buffer, BUFFER_SIZE-1, 0 ) > 0 ){} close( connfd ); }
close( sock ); return 0; }
|
tips:
注意有些socket修改选项(例如本次的修改缓冲区选项),在客户端必须要在connect前修改好,connect调用成功后就已经完成了TCP三次握手。而对于服务器端,有些操作必须要在listen前调用完成,因为连接socket由accept调用返回,accept在listen监听队列中选择的接收连接至少都已经完成了TCP的三次握手的前两个握手动作进入SYN_RCVD状态
系统对于发送和接收缓冲区有一个最小值,设定值小于该值会被默认为最小值
客户端
tcpdump -nt -i eth0 port 255
tcpdump
:
命令行网络抓包工具,用于捕获和分析网络数据包。
-nt
:
-n
表示禁用主机名解析,只显示 IP 地址。
-t
表示不显示时间戳。
-i eth0
:
-i
后接网络接口的名称,这里是
eth0
,表示捕获该网络接口上的数据包。
port 255
:
- 表示捕获目标端口号为
255
的数据包。
port
后接端口号,可以是单个端口、一系列端口范围,或者使用服务名(例如,http
)。
综合起来,该命令的含义是在 eth0
网络接口上捕获目标端口号为 255
的数据包,同时禁用主机名解析,不显示时间戳,将捕获到的数据包的源和目标
IP 地址以及端口号显示出来。