UNIX网络编程之基础

前言

因为socket编程中涉及到的结构比较多,因此在此做了一些记录,方便后续的查阅。


1. 结构体相关

1)sockaddr和sockaddr_in详解

sockaddr在很早的版本使用,其中成员sa_data把通信的IP地址和端口混合在一起,结构体和头文件见如下:

#include <sys/socket.h>

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

sockaddr_in将早期的结构的ip和端口分离出来,结构如下:

#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;
};

sin_port和sin_addr都必须是网络字节序(大端字节序),而我们通常使用的linux等是小端字节序(主机字节序),因此需要使用接口转换。

2) 字节序转换的函数

~~inet_addr~~如今已废除,参考<<UNIX网络编程卷1>>3.6节P68,替代的有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);

注意: 如果端口是0()或INADDR_ANY(值通常为0), 可以不转为网络字节序(NBO),因为使用htonl并非必要,不过头文件<netinet/in.h>中定义了所有INADDR_按照主机字节序定义,我们还是要习惯使用htonl转<<UNIX网络编程卷1>>4.4节P83。
推荐使用inet_pton,支持IPV4和IPV6,只做小小的改动,但仅修改一点点,
其他地方都需要修改,因此更好的做法是编写协议无关的程序(11-11,使用了getaddrinfo函数)

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)问题和结论

3-1)编写协议无关的代码,支持IPv4和IPv6

3-2) 使用域名替代点分十进制的方式


2. API相关

1)connect函数

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

如果是TCP套接字,connect激发TCP的三次握手过程,仅再链接成功或出错返回,出错有如下几种情况:

  • 客户端发送SYN分节,没有收到服务端的响应,返回ETIMEDOUT错误,后续会间隔时间继续发送SYN。
  • 若对客户的SYN响应是RST(表示复位),表明服务器在客户端指定的端口无进程运行,这是一种硬错,客户一接收到RST就马上返回ECONNREFUSED错误。
    客户端运行,服务端不运行
root@ubuntu:/opt/socket/unpv13e/intro# ./daytimetcpcli 127.0.0.1
connect error: Connection refused

抓包:
在这里插入图片描述

扫描二维码关注公众号,回复: 16156627 查看本文章
  • 若客户发出SYN分节路由器转发ICMP错误, 认为是软错误(soft error),客户主机内核保存该消息,按照时间间隔继续发送,如果再持续一段时间(75s)任然没有收到响应, ICMP错误(EHOSTUNREACH或ENETUNREACH)返回给客户端。

2)bind函数

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);//返回0成功,-1出错
  • 客户端一般不调用bing,即使用通配地址(INADDR_ANY)端口0(内核自行分配端口)IP地址和端口内核选择;
  • 服务端一般会指定端口, IPv4地址使用通配地址(INADDR_ANY,其值一般为0),如果服务器没有把IP地址绑到它的套接字上,内核就把客户发送的SYN的目的IP地址作为服务器的源IP地址,如果是IPv6,参考4.4节的方式处理。

在这里插入图片描述

  • 如果让内核来为套接字选择临时端口,想获取分配的端口,需要调用getsockname来返回协议地址。通常指定服务器的端口应该在(5000, 49152)范围,避免与临时端口号(一般大于49152或小于5000 P98详)

  • 从bind函数返回的一个常见错误是EADDRINUSE(地址已被使用)。解决方式使用套接字选项SO_REUSEADDR/SO_REUSEPORT来解决,参考7.5节讨论。

后续开专题记录,这里仅做一个目录性的引导,后续在此链接。

3)listen函数

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

4)accept函数

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

3. 难点

后续开专题记录,这里仅做后续的链接。

未完待续…

猜你喜欢

转载自blog.csdn.net/nc_linux/article/details/125045976