套接字使用记录

一、地址转换

1. inet_addr inet_aton inet_ntoa

inet_addr inet_aton都是字符串转网络字节序,转出来的不需要再htonl
inet_ntoa是把网络字节序转为字符串,所以如果不是网络字节序,转出来的可能跟预期不符

typedef uint32_t in_addr_t;
struct in_addr
{
	in_addr_t s_addr;
};
#define __SOCKADDR_COMMON(sa_prefix)  sa_family_t sa_prefix##family
struct sockaddr_in
{
	__SOCKADDR_COMMON (sin_);			
	in_port_t sin_port;                 /* Port number.  */
	struct in_addr sin_addr;            /* Internet address.  */
	unsigned char sin_zero[sizeof(struct sockaddr) -
						__SOCKADDR_COMMON_SIZE -
						sizeof(in_port_t) -
						sizeof(struct in_addr)];  //填充0 以保持与struct sockaddr同样大小 
};
struct sockaddr
{

   __SOCKADDR_COMMON (sa_);   
   char sa_data[14];          
};
struct sockaddr与struct sockaddr_in长度一致,所有可以安全的相互转换

int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
in_addr_t inet_addr(const char *cp);

举个栗子

void test
{
	unsigned char *ch, i = 0;    	
	unsigned int addr = inet_addr("192.168.128.14");
	printf("addr = %#x\n", addr);	//addr = 0xe80a8c0  14 128 168 192  高地址-》低地址  高尾端/大端
	ch = (unsigned char *)&addr;
	for (; i < 4; i++) {
		printf("%d ", *ch);			//192 168 128 14  低地址-》高地址
		ch++;
	}
	printf("\n");
	
	struct in_addr staddr;
	inet_aton("192.168.128.15", &staddr);
	printf("addr = %#x\n", staddr.s_addr);		//addr = 0xf80a8c0
	
	printf("addr = %s\n", inet_ntoa(staddr));	//addr = 192.168.128.15
}

2. 网络字节序

(1) 使用注意事项1

网络字节序只是针对多字节的数据类型比如short 、int 、long这种数据类型,对于单字节的char或者char数组,不存在。

原始套接字收包:

struct ether_header *peth_hdr;
while(1)
{
   //获取链路层的数据帧
	bzero(buf, sizeof(buf));
	recvlen = recvfrom(sock_raw_fd, buf, sizeof(buf),0,NULL,NULL);
	if (recvlen > 0) 
	{
		peth_hdr = (struct ether_header *)buf;
		//mac地址是数据,不存在网络字节序的问题,直接按顺序打印即可
		printf("dst_mac: %02x:%02x:%02x:%02x:%02x:%02x\n", peth_hdr->ether_dhost[0], 		
		     	peth_hdr->ether_dhost[1], peth_hdr->ether_dhost[2], peth_hdr->ether_dhost[3], 
		     	peth_hdr->ether_dhost[4], peth_hdr->ether_dhost[5]);
		printf("src_mac: %02x:%02x:%02x:%02x:%02x:%02x\n", peth_hdr->ether_shost[0], 
				peth_hdr->ether_shost[1], peth_hdr->ether_shost[2], peth_hdr->ether_shost[3],
			    peth_hdr->ether_shost[4], peth_hdr->ether_shost[5]);
		//类型是short类型,所以必须转为本地字节序,才正确
		printf("ether_type: %x\n", peth_hdr->ether_type);			//8
		printf("ether_type: %x\n", ntohs(peth_hdr->ether_type) );	//0x0800等
		...
	}
	...
}
(2) 使用注意事项2

htons htonl

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

typedef uint16_t in_port_t;
htons(port);
struct sockaddr_in的sin_port是in_port_t网络字节序的16字节,所以要用htons。
htons与htonl等要注意对应使用,不能乱用。

(3) 八字节类型转换
static inline int IsBigEndian(void)  
{  
    const int n = 1;  
    if(*(char *)&n)  
    {  
        return 0;  
    }  
    return 1;  
} 

#define swap64(val) (((val) >> 56) |\
                    (((val) & 0x00ff000000000000ll) >> 40) |\
                    (((val) & 0x0000ff0000000000ll) >> 24) |\
                    (((val) & 0x000000ff00000000ll) >> 8)   |\
                    (((val) & 0x00000000ff000000ll) << 8)   |\
                    (((val) & 0x0000000000ff0000ll) << 24) |\
                    (((val) & 0x000000000000ff00ll) << 40) |\
                    (((val) << 56)))

#define hton64(val) IsBigEndian() ? val : swap64(val)
#define ntoh64(val) hton64(val)

int cwcf_basic_retriev64(char *pmsg, unsigned long long *pvalue)
{
    int len = sizeof(unsigned long long);
    unsigned long long value = 0;
    memcpy(&value, pmsg, len);
    value = ntoh64(value);
    *pvalue = value;
    return len;
}
int cwcf_basic_store64(char *pmsg, unsigned long long value)
{
    int len = sizeof(unsigned long long);
    value = hton64(value);
    memcpy(pmsg, &value, len);
    return len;
}

二. getsockopt setsockopt

getsockopt和setsockopt函数
setsockopt
https://www.cnblogs.com/dpf-learn/p/6124106.html
最常用的就是getsockopt,connect连接失败之后,select监听一段时间,如果可读,再使用getsockopt
获取SO_ERROR的值,如果是0,说明connect成功,否则连接失败。

man connect

EINPROGRESS
The socket is nonblocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsock‐ opt(2) to read the SO_ERROR
option at level SOL_SOCKET to determine whether connect()
completed successfully (SO_ERROR is zero) or unsuccessfully
(SO_ERROR is one of the usual error codes listed here, explaining the
reason for the failure).

getsockopt最后一个参数必须设定值,然后传参。不然会有错误。

举个栗子:
connect非阻塞连接失败之后,使用select监听,未指定getsockopt最后一个参数的长度,导致出问题

FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(fd, &rset);
FD_SET(fd, &wset);
sttime.tv_sec = 5;
sttime.tv_usec = 0;

ret_2 = select(fd + 1, &rset, &wset, NULL, &sttime);
if (ret_2 == 0) {
	printf("select timeout...\n");
}
else if (ret_2 < 0) {
	perror("select error");
}
else {
	int sockerror;
	int len;
	printf("8\n");
	if (!getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerror, &len)) {
		printf("sockerror = %d\n", sockerror);		//sockerror = 64550200
		errno = sockerror;
		if (errno) {
			printf("error:%s\n", strerror(errno));	//error:Unknown error 64550200
		}
		if (sockerror == 0)	ret = 0;
	}
}

getsockopt setsockopt使用最好检测返回值。执行正常返回0,失败返回-1.

三、 fcntl

fcntl一般使用较多的是设置套接字非阻塞,使用F_GETFL F_SETFL,不要无用F_SETFD

四、connect返回EINPROGRESS的处理方式

Linux下connect超时处理(总结)

ret = connect(fd, (const struct sockaddr*)&staddr, sizeof(staddr));
if (ret < 0) 
{
	if (errno != EINPROGRESS) {
		printf("connect error\n");
		return -1;
	}
	
	FD_ZERO(&rset);
	FD_ZERO(&wset);
	FD_SET(fd, &rset);
	FD_SET(fd, &wset);
	sttime.tv_sec = 5;
	sttime.tv_usec = 0;

	ret_2 = select(fd + 1, &rset, &wset, NULL, &sttime);
	if (ret_2 == 0) {
		printf("select timeout...\n");
	}
	else if (ret_2 < 0) {
		perror("select error");
	}
	else {
		int sockerror;
		int len = sizeof(sockerror);
		if (!getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerror, &len)) {				
			if (sockerror) {
				errno = sockerror;
				printf("error:%s\n", strerror(errno));		//sockerror = 64550200
			}
			else {
				ret = 0;
			}
		}
	}
}

if (ret == 0) {
	printf("connect success\n");
	return fd;
}
else {
	printf("connect failed\n");
	close(fd);
}	

五、bind:address already in use

tcp服务器关闭连接后,再次执行服务器,短时间内端口占用得不到释放,bind error
在服务器终止后,再次打开会出现bind:address already in use解决方案
bind:address already in use或者time_wait只有在tcp服务器主动关闭连接,才会有。如果是客户端主动关闭连接,不会产生。

六、EAGAIN、EWOULDBLOCK、EINTR与非阻塞

EAGAIN: Resource temporarily unavailable,errno代码为11(EAGAIN)
EINTR: Interrupted system call
EWOULDBLOCK可以理解为EAGAIN,IPOSIX.1-2001上允许两个任意一个出现都行,所以建议在判断错误码上两个都写上。

if (fds[i].fd == listen_sd) {
	do {
		new_sd = accept(listen_sd, NULL, NULL);
		if (new_sd < 0) {
			if (errno != EWOULDBLOCK) {
				syslog(LOG_WARNING, "  accept() failed");
				end_server = 1;
			}
			break;
		}

		/* Add the new incoming connection to the pollfd structure */
		fds[nfds].fd = new_sd;
		fds[nfds].events = POLLIN;
		nfds++;

		/* Loop back up and accept another incoming connection */
	} while (new_sd != -1);

}

非阻塞式发送tcp报文

int sock_raw_send(int sock, char * pmsg, int length)
{
    int ret = 0;
    int sendlen = 0;
    int count = 3;

    while ((length) && (0 != count)) 
    {
        pmsg += sendlen;
        sendlen = send(sock, pmsg, length, 0);
        if (0 > sendlen) {
            sendlen = 0;
            if ((errno == EINTR) ||
                (errno == EWOULDBLOCK) ||
                (errno == EAGAIN)) {
                count--;
                usleep(2000);
            } else {
                ret = -1;
                break;
            }
        } else {
            length -= sendlen;
        }
	}

    if (length)
        syslog(LOG_ERR, "send failed: %d bytes", length);

	return ret;
}

七、 INADDR_ANY

INADDR_ANY
转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
比如一台电脑有3块网卡,分别连接三个网络,那么这台电脑就有3个ip地址了,如果某个应用程序需要监听某个端口,那他要监听哪个网卡地址的端口呢?

如果绑定某个具体的ip地址,你只能监听你所设置的ip地址所在的网卡的端口,其它两块网卡无法监听端口,如果我需要三个网卡都监听,那就需要绑定3个ip,也就等于需要管理3个套接字进行数据交换,这样岂不是很繁琐?

所以出现INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值