Linux网络编程基础API-学习笔记
主机字节序和网络字节序
大端字节序:网络字节序
利用union公用体查看自己电脑的字节序:
| 12
 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:
| 12
 3
 
 | #include <sys/types.h>#include<sys/socket.h>
 int socket(int domain,int type,int protocol);
 
 | 
| 12
 3
 4
 5
 6
 
 | 
 
 
 
 int sock = socket( PF_INET, SOCK_STREAM, 0 );
 
 | 
bind
将一个socket和socket地址绑定称为给socket命名。
| 12
 3
 
 | #include <sys/types.h>#include<sys/socket.h>
 int bind(int sockfd,const struct sockaddr * myaddr,socklen_taddrlen)
 
 | 
绑定成功返回0,失败返回-1.
监听socket
创建一个监听队列存放待处理的客户连接
| 12
 
 | #include<sys/socket.h>int listen(int sockfd,int backlog)
 
 | 
backlog:内核监听队列的最大长度,
| 12
 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
报错解决:
| 12
 
 | : Cannot assign requested addressa.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
此时端口状态:
| 12
 3
 4
 
 | ❯ lsof -i :255COMMAND    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
| 12
 
 | ❯ netstat -nt |grep 255tcp        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
 | 
接受连接
| 12
 3
 
 | #include <sys/types.h>#include<sys/socket.h>
 int acept(int sockfd,struct sockaddr* addr,socklen_t *addrlen);
 
 | 
addr:被接受的远端socket地址,addrlen地址长度
接受一个异常的连接:
| 12
 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,表示带外数据,用于发送或接受紧急数据
整体程序:
| 12
 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;
 }
 
 | 
服务器接收:
| 12
 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 超时非常有用。
| 12
 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缓冲区
客户端修改发送缓冲区:
| 12
 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选项
socket选项
服务器端
| 12
 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 地址以及端口号显示出来。