Socket for Linux Network Programming (iii): The simplest client/server program

Source: Internet
Author: User
Tags socket error htons

The following describes how to learn the socket API through the simplest client/server program instance.


The function of the echoser. C program is to read characters from the client and then direct the echo back.

C ++ code
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
/*************************************** **********************************
> File name: echoser. c
> Author: Simba
> Mail: dameng34@163.com
> Created time: Fri 01 Mar 2013 06:15:27 pm CST
**************************************** ********************************/

# Include <stdio. h>
# Include <sys/types. h>
# Include <sys/socket. h>
# Include <unistd. h>
# Include <stdlib. h>
# Include <errno. h>
# Include <ARPA/inet. h>
# Include <netinet/in. h>
# Include <string. h>

# Define err_exit (m )\
Do {\
Perror (m );\
Exit (exit_failure );\
} While (0)

Int main (void)
{
Int listenfd; // passive socket (file descriptor), that is, only the accept
If (listenfd = socket (pf_inet, sock_stream, ipproto_tcp) <0)
// Listenfd = socket (af_inet, sock_stream, 0)
Err_exit ("socket error ");

Struct sockaddr_in servaddr;
Memset (& servaddr, 0, sizeof (servaddr ));
Servaddr. sin_family = af_inet;
Servaddr. sin_port = htons (5188 );
Servaddr. sin_addr.s_addr = htonl (inaddr_any );
/* Servaddr. sin_addr.s_addr = inet_addr ("127.0.0.1 ");*/
/* Inet_aton ("127.0.0.1", & servaddr. sin_addr );*/

Int on = 1;
If (setsockopt (listenfd, sol_socket, so_reuseaddr, & on, sizeof (on) <0)
Err_exit ("setsockopt error ");

If (BIND (listenfd, (struct sockaddr *) & servaddr, sizeof (servaddr) <0)
Err_exit ("BIND error ");

If (Listen (listenfd, somaxconn) <0) // listen should be after socket and bind, and before accept
Err_exit ("Listen error ");

Struct sockaddr_in peeraddr; // output parameters
Socklen_t peerlen = sizeof (peeraddr); // input an outgoing parameter, which must have an initial value
Int conn; // connected socket (active socket, that is, active Connect)
If (conn = accept (listenfd, (struct sockaddr *) & peeraddr, & peerlen) <0)
Err_exit ("Accept error ");
Printf ("Recv connect IP = % s Port = % d \ n", inet_ntoa (peeraddr. sin_addr ),
Ntohs (peeraddr. sin_port ));

Char recvbuf [1024];
While (1)
{
Memset (recvbuf, 0, sizeof (recvbuf ));
Int ret = read (Conn, recvbuf, sizeof (recvbuf ));
Fputs (recvbuf, stdout );
Write (Conn, recvbuf, RET );
}

Close (conn );
Close (listenfd );

Return 0;
}


The following describes the socket APIs used in the program. These functions are in sys/socket. h.


Int socket (INT family, int type, int Protocol );
Socket () opens a network communication port. If it succeeds, a file descriptor is returned like open, an application can use read/write to send and receive data over the network like a read/write file. If a socket () call fails,-1 is returned. For IPv4, the family parameter is set to af_inet. For TCP, the type parameter is set to sock_stream, which indicates the stream-oriented transmission protocol. If the UDP protocol is used, the type parameter is specified as sock_dgram, which indicates the datagram-oriented transmission protocol. The Protocol parameter is described in the following code: 0.


Int BIND (INT sockfd, const struct sockaddr * myaddr, socklen_t addrlen );
The network address and port number listened on by the server program are usually fixed. After the client program learns the address and port number of the server program, it can initiate a connection to the server, therefore, the server needs to call bind to bind a fixed network address and port number. If BIND () is successful, 0 is returned. If BIND () fails,-1 is returned.
BIND () is used to bind sockfd and myaddr, so that the file descriptor used for network communication listens to the address and port number described by myaddr. Struct sockaddr * is a common pointer type. The myaddr parameter can actually accept sockaddr structs of multiple protocols, and their lengths are different. Therefore, the third parameter addrlen is required to specify the length of the struct. In our program, the myaddr parameter is initialized as follows:

Memset (& servaddr, 0, sizeof (servaddr ));
Servaddr. sin_family = af_inet;
Servaddr. sin_port = htons (5188 );
Servaddr. sin_addr.s_addr = htonl (inaddr_any );

First, clear the entire struct (you can also use the bzero function), set the address type to af_inet, and the network address to inaddr_any. This macro indicates any local IP address, because the server may have multiple NICs, and each Nic may be bound to multiple IP addresses, this setting can be monitored on all IP addresses, it is not until a connection is established with a client that determines which IP address to use and the port number is 5188.


Int listen (INT sockfd, int backlog );
A typical server program can serve multiple clients at the same time. When a client initiates a connection, the accept () called by the server returns and accepts the connection, if a large number of clients initiate a connection and the server is too late to process it, the client that has not been accept will be in the connection waiting state, and listen () declares that sockfd is in the listening state, in addition, a maximum of backlog clients are allowed to be in connection waiting state. If more connection requests are received, ignore them. If listen () is successful, 0 is returned. If yes,-1 is returned.


Int accept (INT sockfd, struct sockaddr * cliaddr, socklen_t * addrlen );
After the three-party handshake is complete, the server calls accept () to accept the connection. If the server still does not have a client connection request when it calls accept (), it will block waiting until a client connection comes up. Cliaddr is an outgoing parameter. When accept () returns, it returns the outgoing client address and port number. The addrlen parameter is a value-result argument. It is used to pass in the cliaddr length provided by the caller to avoid Buffer Overflow, the actual length of the client address struct is transmitted (it is possible that the buffer provided by the caller is not fully occupied ). If the cliaddr parameter is null, the client address is not concerned.

In the above program, we print the Client IP address and port number connected through peeraddr.


In the while loop, read the client's request from the file descriptor conn returned by accept, and then send it back directly.


The function of echocli. C is to get a line of characters from the standard input, send it to the server, receive it from the server, and print it in the standard output.

C ++ code
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
/*************************************** **********************************
> File name: echoser. c
> Author: Simba
> Mail: dameng34@163.com
> Created time: Fri 01 Mar 2013 06:15:27 pm CST
**************************************** ********************************/

# Include <stdio. h>
# Include <sys/types. h>
# Include <sys/socket. h>
# Include <unistd. h>
# Include <stdlib. h>
# Include <errno. h>
# Include <ARPA/inet. h>
# Include <netinet/in. h>
# Include <string. h>

# Define err_exit (m )\
Do {\
Perror (m );\
Exit (exit_failure );\
} While (0)

Int main (void)
{
Int sock;
If (sock = socket (pf_inet, sock_stream, ipproto_tcp) <0)
// Listenfd = socket (af_inet, sock_stream, 0)
Err_exit ("socket error ");

Struct sockaddr_in servaddr;
Memset (& servaddr, 0, sizeof (servaddr ));
Servaddr. sin_family = af_inet;
Servaddr. sin_port = htons (5188 );
Servaddr. sin_addr.s_addr = inet_addr ("127.0.0.1 ");
/* Inet_aton ("127.0.0.1", & servaddr. sin_addr );*/

If (connect (sock, (struct sockaddr *) & servaddr, sizeof (servaddr) <0)
Err_exit ("Connect error ");

Char sendbuf [1024] = {0 };
Char recvbuf [1024] = {0 };
While (fgets (sendbuf, sizeof (sendbuf), stdin )! = NULL)
{

Write (sock, sendbuf, strlen (sendbuf ));
Read (sock, recvbuf, sizeof (recvbuf ));

Fputs (recvbuf, stdout );

Memset (sendbuf, 0, sizeof (sendbuf ));
Memset (recvbuf, 0, sizeof (recvbuf ));
}

Close (sock );

Return 0;
}


Because the client does not need a fixed port number, you do not need to call BIND (). The client port number is automatically allocated by the kernel. Note: the client does not allow BIND () calls, but does not need to call BIND () to fix a port number. The server does not have to call BIND (), but if the server does not call BIND (), the kernel automatically assigns a listening port to the server. Each time the server is started, the port number is different and the client will have trouble connecting to the server.


Int connect (INT sockfd, const struct sockaddr * servaddr, socklen_t addrlen );
The client needs to call connect () to connect to the server. The parameters of connect and bind are the same. The difference is that the parameters of BIND are their own addresses, while the parameters of Connect are the addresses of the other party. If connect () is successful, 0 is returned. If an error occurs,-1 is returned.


Compile and run the server first:

Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $./echoser


Run the netstat command on another terminal to view the information:

Simba @ Ubuntu :~ $ Netstat-anp | grep 5188
(Not all processes cocould be identified, non-owned process info
Will not be shown, you wowould have to be root to see it all .)
TCP 0 0 0.0.0.0: 5188 0.0.0.0: * Listen 4425/echoser


You can see that the server program listens to port 5188, And the IP address has not been determined yet. Compile and run the client now:

Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $./echocli


Return to the terminal where the server is located and check the server output:

Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $./echoser
Recv connect IP = 127.0.0.1 Port = 59431


The client port number is automatically assigned.

Netstat again

Simba @ Ubuntu :~ $ Netstat-anp | grep 5188
(Not all processes cocould be identified, non-owned process info
Will not be shown, you wowould have to be root to see it all .)
TCP 0 0 0.0.0.0: 5188 0.0.0.0: * Listen 4425/echoser
TCP 0 0 127.0.0.1: 59431 127.0.0.1: 5188 established 4852/echocli
TCP 0 0 127.0.0.1: 5188 127.0.0.1: 59431 established 4425/echoser

A socket file descriptor in the application corresponds to a socket pair, that is, the source address: the source port number and the destination address: the destination port number, also corresponds to a TCP connection.

The first line above is listenfd in echoser. C; the second line is Conn in echocli; and the third line is sock in echoser. C. 4425 and 4852 are process IDs.


Now let's perform a test ~ Annotate the 42 lines of code.

Start the server first, start the client, and use ctrl-C to terminate the server. Then, run the server immediately. The result is:

Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $./echoser
BIND error: address already in use

This is because, although the server application is terminated, the TCP protocol layer connection is not completely disconnected, so you cannot listen to the same server port again. Run the netstat command to view the details:

Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $ netstat-anp | grep 5188
(Not all processes cocould be identified, non-owned process info
Will not be shown, you wowould have to be root to see it all .)
TCP 0 0 127.0.0.1: 5188 127.0.0.1: 37381 fin_wait2-
TCP 1 0 127.0.0.1: 37381 127.0.0.1: 5188 close_wait 2302/echocli

When the server terminates, the socket descriptor automatically closes the concurrent fin segment to the client. After receiving the fin, the client is in the close_wait status, but the client has not terminated or disabled the socket descriptor, therefore, no fin is sent to the server, so the TCP connection of the server is in the fin_wait2 state.


Now use ctrl-C to terminate the client, and then observe the phenomenon:

Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $ netstat-anp | grep 5188
(No info cocould be read for "-P": geteuid () = 1000 but you shoshould be root .)
TCP 0 0 127.0.0.1: 5188 127.0.0.1: 37382 time_wait-
Simba @ Ubuntu :~ /Documents/code/linux_programming/UNP/socket $./echoser
BIND error: address already in use

The socket descriptor is automatically closed when the client ends. The server's TCP connection is in the time_wait status after receiving the fin segment sent by the client. The TCP protocol stipulates that the party that actively closes the connection must be in the time_wait status and wait for the time of the two MSL (maximumsegment lifetime) to return to the closed status, the main reason for the MSL time is that if the last ACK segment is not sent to the recipient during this time period, you can resend it. Because we first Ctrl-C to terminate the server, the server is the party that actively closes the connection and cannot listen to the same server port again during time_wait. MSL is defined as two minutes in rfc1122, but the implementation of each operating system is different. in Linux, server can be started again after half a minute. For details about the time_wait time, refer to unp.
Section 2.7.


It is unreasonable not to allow listening again before the TCP connection of the server is completely disconnected, because the TCP connection is not completely disconnected, which means that connfd (127.0.0.1: 8000) is not completely disconnected, however, we re-listen to listenfd (0.0.0.0: 8000). Although it occupies the same port, different IP addresses correspond to a specific IP address for communication with a client, the listenfd corresponds to the wildcard address. To solve this problem, use setsockopt () to set so_reuseaddr to 1, which indicates that multiple socket descriptors with the same port number but different IP addresses are allowed to be created. Replace the original 40 ~ Open 42 lines of code and solve the problem.

Refer:

Linux C Programming one-stop learning

Chapter 1 TCP/IP details

UNP

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.