Multiple options for sockets
Specific source code can refer to my GitHub
1. Socket options and I/O buffer size
When we do socket programming, we usually only focus on data communication and ignore the different characteristics of sockets. However, it is also important to understand these features and make changes as needed
1.1 multiple options for sockets
Options are layered.
protocol layer | Option name | read | Set up |
---|---|---|---|
SOL_SOCKET | SO_SNDBUF | O | O |
SOL_SOCKET | SO_RCVBUF | O | O |
SOL_SOCKET | SO_REUSEADDR | O | O |
SOL_SOCKET | SO_KEEPALIVE | O | O |
SOL_SOCKET | SO_BROADCAST | O | O |
SOL_SOCKET | SO_DONTROUTE | O | O |
SOL_SOCKET | SO_OOBINLINE | O | O |
SOL_SOCKET | SO_ERROR | O | X |
SOL_SOCKET | SO_TYPE | O | X |
IPPROTO_IP | IP_TOS | O | O |
IPPROTO_IP | IP_TTL | O | O |
IPPROTO_IP | IP_MULTICAST_TTL | O | O |
IPPROTO_IP | IP_MULTICAST_LOOP | O | O |
IPPROTO_IP | IP_MULTICAST_IF | O | O |
IPPROTO_TCP | TCP_KEEPALIVE | O | O |
IPPROTO_TCP | TCP_NODELAY | O | O |
IPPROTO_TCP | TCP_MAXSEG | O | O |
Generally, there are three types of protocol layers
Sol? Socket layer: it is a general option related to socket
IP proto? IP layer: IP protocol related matters
Ipproto? TCP layer: matters related to TCP protocol
1.2 common operations
There are two operations: Get read and Set.
Read function
#include <sys/socket.h> int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen); Success returned 0, failure returned - 1 Socket descriptor Level > protocol layer to view Optname -- > optional item name Optval -- > save the buffer address value of the view result Optlen -- > buffer size passed to the fourth parameter optval
Setting function
#include <sys/socket.h> int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen); Success 0 failed - 1 optval saves the option information buffer address value to be modified Number of bytes of optlen passing optional information Other ibid
An example of calling the getsockopt function is as follows:
- [sokc_type]
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> void error(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main() { int TCP_sock, UDP_sock; int sock_type; socklen_t optlen; int state; TCP_sock = socket(PF_INET, SOCK_STREAM, 0); UDP_sock = socket(PF_INET, SOCK_DGRAM, 0); printf("SOCK_STREAM: %d\n SOCK_DGRAM: %d\n", SOCK_STREAM, SOCK_DGRAM); printf("PF_INET: %d\n", PF_INET); state = getsockopt(TCP_sock, SOL_SOCKET, SO_TYPE, (void*) &sock_type, &optlen); if (state) error("getsockopt() error"); printf("TCP_sock type : %d \n optlen: %d\n", sock_type, optlen); state = getsockopt(UDP_sock, SOL_SOCKET, SO_TYPE, (void*) &sock_type, &optlen); if (state) error("getsockopt() error"); printf("UDP_sock type : %d \n optlen: %d\n", sock_type, optlen); close(TCP_sock); close(UDP_sock); return 0; }
- Note: sometimes there will be errors when running. Just run it several times more.
- Socket type can only be determined after creation, and cannot be changed later.
Compile run:
gcc sock_type.c -o sock_type ./sock_type
Operation result:
SOCK_STREAM: 1 SOCK_DGRAM: 2 PF_INET: 2 TCP_sock type : 1 optlen: 4 UDP_sock type : 2 optlen: 4
1.3 I/O buffer size
I/O buffer will be generated at the same time when socket is generated.
So? Rcvbuf is the optional input buffer size, so? Sndbuf is the optional output buffer size
Read and change cases
read
- [get_buf.c]
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> void error(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main() { int sock; int snd_buf, rcv_buf, state; socklen_t len; sock = socket(PF_INET, SOCK_STREAM, 0); len = sizeof(snd_buf); state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len); if (state) error("getsockopt() error"); len = sizeof(rcv_buf); state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len); if (state) error("getsockopt() error"); printf("INPUT buffer: %d\n", rcv_buf); printf("OUTput buffer: %d\n", snd_buf); close(sock); return 0; }
Compile run:
gcc get_buf.c -o get_buf ./get_buf
Operation result:
INPUT buffer: 131072 OUTPUT buffer: 16384
This is my I / O buffer size
change
- [set_buf.c]
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> void error(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char* argv[]) { int sock, state; int snd_buf = 1024*3; int rcv_buf = 1024*3; socklen_t len; sock = socket(PF_INET, SOCK_STREAM, 0); len = sizeof(rcv_buf); state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rcv_buf, sizeof(rcv_buf)); if (state) error("setsockopt() 1 error"); len = sizeof(snd_buf); state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&snd_buf, sizeof(snd_buf)); if (state) error("setsockopt() 2 error"); state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rcv_buf, &len); if (state) error("getsockopt() 1 error"); state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&snd_buf, &len); if (state) error("getsockopt() 2 error"); printf("INPUT :%d\n", rcv_buf); printf("OUTPUT :%d\n", snd_buf); close(sock); return 0; }
Compile run:
gcc set_buf.c -o set_buf ./set_buf
Operation result:
INPUT : 6144 OUTPUT : 6144
The running result is not as expected, because the buffer size setting needs to be very careful, it will not be completely changed according to our requirements, and it will automatically guarantee a little space.
2. SO_REUSEADDR
Changing the state of the option so ﹣ reuseaddr can determine whether the socket port number in the time wait state can be assigned to a new socket.
The default value of 0 means that a new socket cannot be assigned in this state. Changing to 1 means that it can be assigned.
2.1 what is the time wait state
When a socket disconnects, it experiences four handshakes, whether it calls close() or Ctrl +c
Disconnect the connection first, send the FIN message first, and the received one will turn on the time wait state. In order to wait and judge whether your ACK has been sent, if your ACK can't be sent, the socket will be closed. If the other end times out, it will mistakenly think that the FIN hasn't been sent until it has been resend. So it's just to wait for the FIN not to be resend to judge whether your send is successful. So the general waiting time is about 3 minutes .
In fact, both the server and the client have to go through a period of time wait process. The socket that disconnects first must go through the time wait process. However, since the port of the client socket is arbitrary, there is no need to pay too much attention to the time wait state
That's why address allocation fails when running the same disconnected meeting immediately after disconnection.
2.2 change the so? Reuseaddr option
To determine that the same port will run a new socket immediately after shutdown.
- [reuseadr_eserver.c]
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define mx 1024 void error_handling(char *message); int main(int argc, char* argv[]) { int ser_sock, cli_sock; struct sockaddr_in ser_adr, cli_adr; socklen_t cli_adr_sz; int state, str_len; char message[mx]; ser_sock = socket(PF_INET, SOCK_STREAM, 0); if (ser_sock == -1) error_handling("socket() error"); int option; int optlen = sizeof(option); option = 1; state = setsockopt(ser_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&option, optlen); if (state) error_handling("setsockopt() error"); memset(&ser_adr, 0, sizeof(ser_adr)); ser_adr.sin_family = AF_INET; ser_adr.sin_addr.s_addr = htonl(INADDR_ANY); ser_adr.sin_port = htons(atoi(argv[1])); if (bind(ser_sock, (struct sockaddr*)&ser_adr, sizeof(ser_adr)) == -1) error_handling("bind() error"); if (listen(ser_sock, 5) == -1) error_handling("listen() error"); cli_adr_sz = sizeof(cli_adr); cli_sock = accept(ser_sock, (struct sockaddr*)&cli_adr, &cli_adr_sz); if (cli_sock == -1) error_handling("socket() error_cli"); while ((str_len = read(cli_sock, message, sizeof(message))) != 0) { write(cli_sock, message, str_len); // write(1, message, str_len); } // shutdown(ser_sock, SHUT_WR); close(cli_sock); close(ser_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
Compile run:
gcc reuseadr_eserver.c -o reuseadr_eserver ./reuseadr_eserver 9190 ^c ./reuseadr_eserver 9190
Operation result:
No report error
Phenomenon: the client can only be closed after the server is closed. After the server is closed, it can reconnect before the client is closed.
3. TCP_NODELAY
Set the usage status of the Nagle algorithm.
3.1 what is the Nagle algorithm
Nagle algorithm is to send the next data only after receiving the reply ACK of the previous data. Using and sending short messages can improve the network transmission efficiency.
Because it can send more data to the buffer next time when it is waiting, so as to reduce the use of packets. Each packet will have header information. Reducing the use can also reduce the network traffic.
This is only applicable to small file transfer, while the input and output buffers of large file transfer will not take too much time, and will be transferred when the data buffer package is full, so it will not increase the use of data packages. Therefore, if the Nagle algorithm is not used, the efficiency can be greatly improved.
3.2 disable Nagle algorithm
Just change TCP? Nodelay to 1 (true)
int opt_val = 1 setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*) &opt_val, sizeof(opt_val)); // Set up socklen_t opt_len = sizeof(opt_val); getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*) &opt_val, &opt_len ); //See