Fundamentals of UNIX Network Programming

foreword

Because there are many structures involved in socket programming, some records are made here to facilitate subsequent reference.


1. Structure related

1) Detailed explanation of sockaddr and sockaddr_in

sockaddrUsed in very early versions, where members sa_datamix IP addresses and ports for communication, the structure and header files are as follows:

#include <sys/socket.h>

struct sockaddr {
	sa_family_t 				sin_family;
	char 						sa_data[14];	
}

sockaddr_inSeparate the ip and port of the early structure, the structure is as follows:

#include <netinet/in.h>
#include <arpa/inet.h>

struct sockaddr_in
{
	sa_family_t 				sin_family;
	uint16_t 					sin_port;
	struct in_addr				sin_addr;
	char						sin_zero[8];
};
struct in_addr
{
	in_addr_t					s_addr;
};

Both sin_port and sin_addr must be in network byte order (big endian), and the linux we usually use is little endian (host byte order), so interface conversion is required.

2) Functions for byte order conversion

~~ inet_addr~~ It has been abolished now, refer to <<UNIX Network Programming Volume 1>> Section 3.6 P68, the replacement is inet_addr,更好的替代是inet_pton(支持IPv4和IPv6)

#include <arpa/inet.h>
/*返回:若字符串有效则为1,否则为0/*/
int inet_aton(const char *strptr, struct in_addr *addrptr);
/*返回:若字符串有效则为32位二进制网络字节序的IPv4地址,否则为INADDR_NONE*/
in_addr_t inet_addr(const char *strptr);
/*返回:指向一个点分十进制数串的指针*/
char *inet_ntoa(struct in_addr inaddr); 

/*返回:若成功则为1,若输入不是有效的表达式则为0, 若出错则为-1*/
int  inet_pton(int family, const char *strptr, void *addrptr/*&foo.sin_addr*/);
/*如成功则为指向结果的指针,若出错则为NULL*/
len:如果是IPv4,填写16,在<netinet/in.h>中有定义宏 
#define INET_ADDRSTRLEN 16 
#define INET6_ADDRSTRLEN 46
char str[INET_ADDRSTRLEN];
ptr = inet_ntop(AF_INET, &foo.sin_addr, str, sizeof(str));
const char *inet_ntop(int family, const void *addrptr,  char *strptr, size_t len);

e.g.:

struct sockaddr_in servaddr;
servaddr.sin_port = htons(13);
servaddr.sin_addr.s_addr = inet_addr("192.168.1.1");
//尽量使用如下,因为该接口不仅支持IPV4还支持IPV6

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int  inet_pton(int af, const char *src, void *dst); //将"点分十进制"->"二进制整数”
//struct  in_addr s;
char *ip = "192.168.1.1";
inet_pton(AF_INET, (void*) ip,  (void*)&s);

Note: If 端口是0() or INADDR_ANY(the value is usually 0), it is not necessary to convert to network byte order (NBO), because it is not necessary to use htonl, but all INADDR_ are defined in the header file <netinet/in.h> according to the host word For the sequence definition, we still have to get used to using htonl to <<UNIX Network Programming Volume 1>> Section 4.4 P83.
It is recommended to use inet_pton, support IPV4 and IPV6, only make small changes, but only a little bit,
other places need to be modified, so the better way is to write 协议无关the program (11-11, using the getaddrinfo function)

struct sockaddr_in ->struct sockaddr_in6
socket(AF_INET, SOCK_STREAM,0) 	-> socket(AF_INET6, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET	->	servaddr.sin6_family = AF_INET6;
servaddr.sin_port 				->		servaddr_sin6_port
inet_pton(AF_INET,...,...)		->		inet_pton(AF_INET6,...,...);

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd, n, counter = 0;
	char				recvline[MAXLINE + 1];
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: a.out <IPaddress>");

	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		err_sys("socket error");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;	/*协议族成员设置为AF_INET*/
	servaddr.sin_port   = htons(13);	/* daytime server */
	if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
		err_quit("inet_pton error for %s", argv[1]);

	if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
		err_sys("connect error");

	while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
		counter++;
		recvline[n] = 0;	/* null terminate */
		if (fputs(recvline, stdout) == EOF)
			err_sys("fputs error");
	}
	if (n < 0)
		err_sys("read error");

	printf("counter = %d\n", counter);
	exit(0);
}

3) Questions and conclusions

3-1) Write protocol-independent code to support IPv4 and IPv6

3-2) Use the domain name instead of dotted decimal


2. API related

1) connect function

#include <sys/socket.h>
/*返回: 若成功则为0,若出错则为-1*/
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

If it is a TCP socket, connect triggers the TCP three-way handshake process, only if the connection succeeds or 出错返回the error occurs in the following situations:

  • The client sends a SYN segment, but does not receive a response from the server, returns an ETIMEDOUT error, and will continue to send SYN at intervals.
  • If the SYN response to the client is RST(meaning reset), it means that the server is running without process on the port specified by the client.
    The client is running, the server is not running
root@ubuntu:/opt/socket/unpv13e/intro# ./daytimetcpcli 127.0.0.1
connect error: Connection refused

Capture packets:
insert image description here

  • If the client sends a SYN segment and the router forwards an ICMP error, it is considered a soft error (soft error), and the client host kernel saves the message and continues to send it according to the time interval. If no response is received after a period of time (75s), it is an ICMP error. (EHOSTUNREACH or ENETUNREACH) returned to the client.

2) bind function

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);//返回0成功,-1出错
  • The client generally does not call bing, that is, 使用通配地址(INADDR_ANY)it 端口0(内核自行分配端口)is selected with the IP address and port kernel;
  • The server generally specifies the port and uses the IPv4 address 通配地址(INADDR_ANY,其值一般为0). If the server does not bind the IP address to its socket, the kernel will take the destination IP address of the SYN sent by the client as the source IP address of the server. If it is IPv6, refer to 4.4 Sections are processed.

insert image description here

  • If you let the kernel choose an ephemeral port for the socket, and want to get the assigned port, you need to call getsockname to return the protocol address. Usually the port of the specified server should be in the range of (5000, 49152), avoiding the temporary port number (generally greater than 49152 or less than 5000 P98 details)

  • A common error returned from the bind function is EADDRINUSE (address already in use). The solution is to use the socket option SO_REUSEADDR/SO_REUSEPORT, as discussed in section 7.5.

Follow-up to open thematic records, here is only a catalog guide, follow-up link here.

3) listen function

#include <sys/socket.h>
int listen(int sockfd, int backlog);//成功返回0, 出错返回-1

4) accept function

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);//成功非负描述符,出错-1

3. Difficulties

Follow-up special records, here are only follow-up links.

To be continued...

Guess you like

Origin blog.csdn.net/nc_linux/article/details/125045976