1.1 socket是一个应用编程的接口,它是一种特殊的文件描述符(对它执行IO的操作函数,比如,read(),write(),close()等操作函数)
1.2 socket代表着网络编程的一种资源
1.3 socket的类型:
流式套接字(SOCK_STEAM): 唯一对应着TCP
提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送顺序接收。内置流量控制,避免数据流淹没接收方。
数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM):唯一对应着UDP
提供无连接服务。数据包以独立数据包的的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW):对应着多个协议,发送穿透了传输层
可以对较低层次协议如IP,ICMP直接访问
IPV4地址:
点分形式: 192.168.7.246
32位整数
特殊IP地址:
局域网IP: 192.XXX.XXX.XXX 10.XXX.XXX.XXX 广播IP: xxx.xxx.xxx.255, 255.255.255.255(全网广播) 组播IP: 224.XXX.XXX.XXX~239.xxx.xxx.xxx
16位的数字(1-65535)
众所周知端口: 1~1023(FTP: 21,SSH: 22, HTTP:80, HTTPS:443)
保留端口: 1024-5000(不建议使用)
可以使用的:5000~65535
TCP端口和UDP端口是相互独立的
网络里面的通信是由 IP地址+端口号 来决定
字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端问题
如CPU访问的是字符串,则不存在大小端问题
一般来说:
X86/ARM: 小端
powerpc/mips, ARM作为路由器时,大端模式
本地字节序、网络字节序
IP地址转换函数:
in_addr_t inet_addr(const char *cp);
cp: 点分形式的IP地址,结果是32位整数(内部包含了字节序的转换,默认是网络字节序的模式)
特点: 1. 仅适应于IPV4
2. 当出错时,返回-1
3.此函数不能用于255.255.255.255的转换
inet_pton()/inet_ntop() #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);
特点: 1.适应于IPV4和IPV6
2.能正确的处理255.255.255.255的转换问题
参数:
1. af: 地址协议族(AF_INET或AF_INET6)
2. src:是一个指针(填写点分形式的IP地址[主要指IPV4])
3. dst: 转换的结果给到dst
RETURN VALUE
inet_pton() returns 1 on success (network address was successfully converted). 0 is returned if src
does not contain a character string representing a valid network address in the specified address family.
If af does not contain a valid address family, -1 is returned and errno is set to EAFNOSUPPORT.
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol);
1.domain:
AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_UNIX, AF_LOCAL Local communication unix(7) AF_NETLINK Kernel user interface device netlink(7) AF_PACKET Low level packet interface packet(7)
2.type:
SOCK_STREAM: 流式套接字 唯一对应于TCP
SOCK_DGRAM: 数据报套接字,唯一对应着UDP
SOCK_RAW: 原始套接字
3.protocol: 一般填0,原始套接字编程时需填充
RETURN VALUE
On success, a file descriptor for the new socket is returned.
On error, -1 is returned, and errno is set appropriately.
成功时返回文件描述符,出错时返回为-1
int fd = -1,fd_client; int result = 0; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; short int SERV_PORT = 5050; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET,SOCK_STREAM,0); if (fd < 0) { perror("socket"); return -1; }
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd: 通过socket()函数拿到的fd
addr: struct sockaddr的结构体变量的地址
addrlen: 地址长度
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
/*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr,sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 #if 1 //mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 绑定 */ result = bind(fd,(struct sockaddr*)&mysockaddr,sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; }
如果是IPV6的编程,要使用struct sockddr_in6结构体(详细情况请参考man 7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
sockfd: 通过socket()函数拿到的fd backlog: 同时允许几路客户端和服务器进行正在连接的过程(正在三次握手) 一般填5, 测试得知,ARM最大为8 内核中服务器的套接字fd会维护2个链表: 1.正在三次握手的的客户端链表(数量=2*backlog+1) 2.已经建立好连接的客户端链表(已经完成3次握手分配好了newfd) 比如:listen(fd, 5); //表示系统允许11(=2*5+1)个客户端同时进行三次握手
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
/*3. 调用listen()把主动套接字变成被动套接字 */ result = listen(fd,5); if (result != 0) { perror("listen"); return -1; }
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <sys/socket.h> int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
sockfd: 经过前面socket()创建并通过bind(),listen()设置过的fd addr和addrlen: 获取连接过来的客户的信息
RETURN VALUE
On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket.
On error, -1 is returned, and errno is set appropriately.
成功时返回已经建立好连接的新的newfd
/*4. 阻塞等待客户端连接请求 */ #if 0 bzero((void*)&clientscokaddr, sizeof(clientscokaddr)); fd_client = accept(fd,NULL,NULL); if (-1 == result) { perror("accpet"); return 0; } #else /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ bzero((void*)&clientscokaddr, sizeof(clientscokaddr)); fd_client = accept(fd, (struct sockaddr*)&clientscokaddr, &clientaddrlen); if (-1 == result) { perror("accpet"); return 0; } char ipv4_addr[16]; if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); #endif
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 通过socket()函数拿到的fd addr: struct sockaddr的结构体变量的地址 addrlen: 地址长度
RETURN VALUE
If the connection or binding succeeds, zero is returned. On error, -1
is returned, and errno is set appropriately.
result = connect(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("connect"); return -1; }