龙空技术网

Linux网络编程——详解SOCKET

Hu先生Linux后台开发 195

前言:

今天朋友们对“c web socket”大概比较关心,小伙伴们都想要分析一些“c web socket”的相关内容。那么小编同时在网摘上收集了一些有关“c web socket””的相关文章,希望看官们能喜欢,兄弟们一起来了解一下吧!

一、预备知识大端模式、小端模式大端字节序(Big Endian):最高有效位存于最低内存地址处,最低有效位存于最高内存处;小端字节序(Little Endian):最高有效位存于最高内存地址,最低有效位存于最低内存处。网络字节序我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,因此网络数据流应采用大端字节序,即低地址高字节可以调用以下库函数做网络字节序和主机字节序的转换

#include <arpa/inet.h>//这些函数调用成功后返回处理后的值,调用失败则返回-1uint32_t htonl(uint32_t hostlong);	//主机字节顺序转换为网络字节顺序 对无符号长型进行操作4bytesuint16_t htons(uint16_t hostshort); //主机字节顺序转换为网络字节顺序 对无符号短型进行操作2bytes  uint32_t ntohl(uint32_t netlong);   //网络字节顺序转换为主机字节顺序 对无符号长型进行操作4bytesuint16_t ntohs(uint16_t netshort);  //网络字节顺序转换为主机字节顺序 对无符号短型进行操作2bytes
IP地址转换函数Linux提供了用于将点分十进制表示的IP地址与二进制表示的IP地址相互转换的函数族

早期

#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>//数字加点类型 转换成 将32位的IP    IP地址存放在参数straddr中,返回结果存放在addrptr中 int inet_aton(const char *straddr, struct in_addr *addrptr);//将32位的IP 转换成 数字加点类型char *inet_ntoa(struct in_addr straddr);//数字加点类型 转换成 将32位的IPin_addr_t inet_addr(const char *cp);/*只能处理IPv4的ip地址不可重入函数注意参数是struct in_addr*/

现在

#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst);const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);/*支持IPv4和IPv6可重入函数*/
Linuxc/c++服务器开发高阶视频,电子书学习资料后台私信【架构】获取sockaddr数据结构Linux中定义了一种通用的套接字结构类型strcut sockaddr,以供不同的协议调用strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结

构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个地址类型是sockaddr_in还是sockaddr_in6(文章没有列举出),由地址族确定,然后函数内部再强制类型转化为所需的地址类型

#include <sys/socket.h>struct sockaddr {	unsigned short sa_family; /* address族, AF_xxx */	char sa_data[14]; 	      /* 14 bytes的协议地址 */};

参数sa_family可选择如下

AF_INET IPv4协议AF_INET6 IPv6协议AF_LOCAL UNIX协议AF_LINK 链路地址协议AF_KEY 密钥套接字

除了sockaddr以外,Linux中还定义了另外一种结构类型sockaddr_in,它和sockaddr等效且可以互相转换(需要显式转换),通常在涉及TCP/IP的编程协议中使用

#include <netinet/in.h>struct sockaddr_in {	short int sin_family; 			/* Internet地址族 */	unsigned short int sin_port;    /* 端口号 */	struct in_addr sin_addr; 		/* Internet地址 */	unsigned char sin_zero[8]; 		/* 添0(和struct sockaddr一样大小)*/};//其中in_addr由于历史设计原因导致结构体多余struct in_addr{__be32 s_addr;//32位IPv4地址,网络字节序};
网络设计模式c/s 客户端/服务器需要开发客户端服务器,采用自定义协议必须先下载客户端,数据提前缓冲好需要考虑安全问题开发工作量大b/s web/服务器不需要安装软件,点击浏览器就可以看到工作量小,客户端基本浏览器方式缺点:必须遵循http协议,动态加载数据二、SOCKET概述linux中的网络编程通过socket接口实现。socket既是一种特殊的IO,它也是一种文件描述符

socket可以简单理解成为一个插座和插排,那么如何匹配?

就是通过IP+端口号进行匹配,匹配之后可以通过socket进行数据的发送和接收(socket本质是文件描述符fd)

具体的流程如下

socket创建

#include <sys/types.h> #include <sys/socket.h>int socket(int domain, int type, int protocol);
domain:

AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址

AF_INET6 与上面类似,不过是采用IPv6的地址

AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用type:

(1)SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

(2)SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

(3)SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的

接受才能进行读取。

(4)SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使

用该协议)

(5)SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数

据包的顺序protocol:

0 默认协议返回值:

成功返回一个新的文件描述符(也叫监听套接字),失败返回-1bind绑定在创建了套接字之后需要IP和端口号和套接字绑定在一起( IP地址:在网络环境中,唯一标识一台主机,端口号:在主机中唯一标识一个进程)前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度

#include <sys/types.h>#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:

socket文件描述符addr:

构造出IP地址加端口号addrlen:

sizeof(addr)长度返回值:

成功返回0,失败返回-1, 设置errno

例如

struct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr));//清0结构体servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(8000);
listen创建了套接字之后通常需要等待客户端的连接,此时可以使用listen函数将该套接字转换为倾听套接字。可以指定同时连接的最大客户端数量若达到数量上限,新客户端等待其它已链接的客户端链接结束
#include <sys/types.h>#include <sys/socket.h>int listen(int sockfd, int backlog);
sockfd:

socket文件描述符backlog:

排队建立3次握手队列和刚刚建立3次握手队列的连接数和返回值

成功返回0,失败返回-1accept当服务器倾听到一个连接之后,可以使用函数accept从倾听套接字的完成连接队列中接收一个连接,如果这个完成连接队列为空,则会使得这个进程进入睡眠状态

#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf:

socket文件描述符addr:

传出参数,返回链接客户端地址信息,含IP地址和端口号addrlen:

传入传出参数,传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小返回值:

成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errnoconnect客户端连接函数客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址

#include <sys/types.h> #include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:

socket文件描述符addr:

传入参数,指定服务器端地址信息,含IP地址和端口号addrlen:

传入参数,传入sizeof(addr)大小返回值:

成功返回0,失败返回-1,设置errno读写函数

<unistd.h>int read(int fd, char *buf, int len);int write(int fd, char *buf, int len);
fd

套接字描述符;buf

指定数据缓冲区;len

指定接收或发送的数据量大小(以字节为单位)。返回值

返回读/写成功的数据量大小,失败则返回-1。关闭函数

<unistd.h>int close(int fd);
fd

套接字描述符;写一个服务器例子和客户端例子

服务器

#include <stdio.h> #include <stdlib.h> #include <string.h>#include <errno.h> #include <sys/types.h> #include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define Port 6666 //端口号#define MAXCLIENT 10 //最大客户端数量int main(int argc, char argv[]){	int socket_fd, client_fd;	int ret;	int addr_size;	struct sockaddr_in server_addr;   	struct sockaddr_in client_addr; 		int read_size;	char buffer[1024]; 		//创建socket	socket_fd = socket(AF_INET, SOCK_STREAM, 0);	if( socket_fd == -1)	{		printf("socket error\n");		exit(1);	}		//绑定bind	bzero(&server_addr, sizeof(struct sockaddr_in));//清空数据		server_addr.sin_family = AF_INET;//IPv4	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将主机IP转换为网络IP	server_addr.sin_port = htons(Port);//将主机端口转换为网络Port			ret = bind(socket_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));	if(ret == -1)	{		printf("bind error\n");		exit(1);	}		//监听	ret = listen(socket_fd, MAXCLIENT);	if(ret == -1)	{		printf("listen error\n");		exit(1);	}		while(1)	{		//accept		addr_size = sizeof(struct sockaddr_in);		client_fd = accept(socket_fd, (struct sockaddr *)(&client_addr), &addr_size);		if(client_fd == -1)		{			printf("accept error\n");			exit(1);		}		//打印客户端IP   将网络地址转换成 .字符串 		printf("Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));					if((read_size = read(client_fd, buffer, 1024)) == -1)    		{     			printf("Read Error\n");     			exit(1);    		}   	     		buffer[read_size]='\0';   		printf("Server received %s\n",buffer); 					close(client_fd);    /* 循环下一个 */   			}		close(socket_fd);   		return 0;}

客户端

#include <stdio.h> #include <stdlib.h> #include <string.h>#include <errno.h> #include <sys/types.h> #include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define Port 6666int main(int argc, char argv[]){	int socket_fd;	int ret;	char buff[1024];	struct sockaddr_in server_addr;		char* str_IP = "172.21.252.7";		//创建客户端socket	socket_fd = socket(AF_INET, SOCK_STREAM, 0);	if( socket_fd == -1)	{		printf("socket error\n");		exit(1);	}		//连接connect	bzero(&server_addr, sizeof(struct sockaddr_in));//清空数据		server_addr.sin_family = AF_INET;//IPv4	server_addr.sin_addr.s_addr = inet_addr(str_IP);//将主机IP转换为网络IP	server_addr.sin_port = htons(Port);//将主机端口转换为网络Port	ret = connect(socket_fd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr_in));	if(ret == -1)	{		printf("connect error\n");		exit(1);	}		while(1)	{		//连接成功了,发送数据		printf("Please input char:\n");     		fgets(buff, 1024, stdin);   		write(socket_fd, buff, strlen(buff)); 	}		close(socket_fd);	return 0;}

运行结果如下

注意

可通过nc指令测试服务器是否有误

标签: #c web socket