1、套接字概念
Socket在Linux环境下,用于标识进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
2、网络字节序
- 大端字节序-低地址高字节,高地址低字节
- 小端字节序-高地址高字节,低地址低字节
tcp/ip协议规定,网络数据流应该采用大端字节序,即低地址高字节。
调用以下库函数做网络字节序和主机字节序的转换
#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);
h表示host,n表示network,l表示32位长整型,s表示16位短整型
3、sockaddr数据结构
struct sockaddr:16位地址类型+14字节地址数据
struct sockaddr_in:16位地址类型AF_INET+16位端口号+32位IP地址+8字节填充
struct sockaddr{
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
struct sockaddr_in{
sa_family_t sin_family; /* address family, AF_xxx */
int port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Ineternet address */
struct in_addr{
uint32_t s_addr; /* address in network */
};
IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,但是sock API的实现早于ANSI C标准化,那时还没有void*类型,因此在bind、accept函数的参数都用struct sockaddr*类型表示,在传递参数前要强制转换,例如:
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)); /* initialize servaddr */
4、IP地址转换函数
#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);
# af: 取值为AF_INET或AF_INET6
# src: 指针,指向保存IP地址字符串形式的字符串。
# dst: 指向存放网络地址的结构体的首地址
# inet_pton,inet_ntop不仅可以转换IPv4的in_addr,还可以转换为IPv6的in6_addr。
例子:
#include<stdio.h>
#include<string.h>
#include<arpa/inet.h>
int main(){
char ip[] = "2.3.4.5";
char server_ip[64];
struct sockaddr_in server_addr;
inet_pton(AF_INET, ip, &server_addr.sin_addr.s_addr);
printf("addr: %x\n", server_addr.sin_addr.s_addr);
printf("addr from net: %x\n", ntohl(server_addr.sin_addr.s_addr));
inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, server_ip, 64);
printf("server ip: %s\n", server_ip);
return 0;
}
5、socket函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
输入:
domain:(产生socket的协议类型)
AF_INET:IPv4
AF_INET6:IPv6
AF_UNIX: 本地协议,客户端、服务端在同一台机器上时使用
type:
SOCK_STREAM:TCP协议
SOCK_DGRAM:UDP协议
SOCK_SEQPACKET:
SOCK_RAW:
SOCK_RDM:
protocol:
为0代表使用默认协议
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
socket()用于打开一个网络通讯端口,就想open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。
6、bind函数
#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
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。
bind()的作用是将参数sockfd与addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。
struct sockaddr* 是一个通用指针类型,addr参数实际上可以接收多种协议的sockaddr结构体,而他们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。
7、listen函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
输入:
sockfd:socket文件描述符
backlog: 在Linux系统中,指排队等待建立三次握手队列长度
系统的backlog会限制监听数的大小
查看系统默认backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
改变系统默认的backlog
vim /etc/sysctl.conf
最后添加
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 1024
保存,然后执行
sysctl -p
典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accpet()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
8、accept函数
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
输入:
sockdf:
socket文件描述符
addr:
传出参数,返回连接客户端地址信息,含IP地址和端口号
addrlen:
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
三次握手完成后,服务器调用accept()接收连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
addr是一个传出参数,accept()返回时传出客户端的地址和端口号。a
ddrlen参数是一个传入传出参数,传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度。
如果给addr参数设置为NULL,表示不关心客户端的地址。
例子:
while(1){
int client_addr_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
n = read(client_fd, buf, MAXLINE);
...
close(client_fd);
}
9、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
客户端需要调用conncet()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
10、出错处理函数
#include<errno.h>
#include<string.h>
char *strerror(int errnum);
输入:
传入参数,错误编号的值,一般取errno的值
返回值:
错误原因