主机字节序与网络字节序
常用结构体
- gethostbyname()可用于获取主机的hostent
- gethostbyname(const char *name)可以对域名解析获得IP地址,更加灵活。
示例
struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
memset(&servaddr, 0, sizeof(servaddr)); //对结构体清零
servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
// servaddr.sin_addr.s_addr = inet_addr("192.168.190.134"); // 指定ip地址。
servaddr.sin_port = htons(atoi(argv[1])); // 指定通信端口 atoi将数字字符串转化为整数
- void memset(void s, int ch, size_t n);
函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 。 - INADDR_ANY
转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 作用:给我们的socket绑定端口号与具体地址
- 示例:
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0。
- 其中listened为一个socket的值; servaddr为一个sockaddr结构体(这里由sockaddr_in结构体转换);第三个参数为第二个参数的长度。
- 服务端程序的端口释放后可能会处于TIME_WAIT状态,需等待两分钟后才能被使用,解决方式:
int opt = 1;
unsigned int len = sizeof(opt);
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
listen(),connect(),accept()函数
当使用tcp服务器使用socket创建通信文件描述符,bind绑定了文件描述符,服务器ip和端口号,listen将服务器端的主动描述符转为被动描述符进行监听之后,接口accept通过三次握手与客户端建立连接
- 服务端在调用listen之前,客户端不能向服务端发起连接请求
- 服务端调用listen之后,服务端socket开启监听客户端的连接
- 客户端调用connect向服务端发送连接请求
- 在TCP底层,客户端和服务端握手后建立起通信通道,如果有多个客户端请求,在服务端就会形成一个已准备好的连接队列
- 服务端调用accept从队列中获取一个已准备好的连接,函数返回一个新的socket,新的socket用于与客户端通信,listen的socket只负责监听客户端的连接请求
listen()函数
int listen(int sockfd, int backlog);
- 作用: 当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求。
- listen函数的第二个参数规定了内核应为相应套接字排队的最大连接个数(backlog+1)
本函数通常应该在调用socket和bind之后,并在调用accept之前被调用。
connect()函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd是由socket函数返回的套接字描述符,第二个、第三个参数分别是一个指向套接字地址结构的指针和结构的大小。
客户端在调用connect前不必非得调用bind函数,因为如果需要的话,内核会确认源IP地址,并选择一个临时端口作为源端口,通常我们都不会在客户端调用bind函数。
accept()函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 函数功能:
被动监听客户端发起的tcp连接请求,三次握手后连接建立成功。客户端connect函数请求发起连接。
连接成功后服务器的tcp协议会记录客端的ip和端口,如果是跨网通信,记录ip的就是客户端所在路由器的公网ip - 返回值:
成功:返回一个通信描述符,专门用于与连接成功的客户端进行通信。
失败:返回-1 ,并设置errno - 函数参数:
a. sockfd 已经被listen转为了被动描述符的“套接字文件描述符”,专门用于客户端的监听,入股sockfs没有被listen函数转为被动描述符,则accept是无法将其用来监听客户端连接的。
套接字文件描述符默认是阻塞的,即如果没有客户端请求连接的时候,此时accept会阻塞,直到有客户端连接;如果不想套接字文件描述符阻塞,则可以创建套接字 socket函数 时指定type为SOCK_NOBLOCK
b. addr: 用于记录发起连接请求的那个客户端的IP端口
建立连接时服务器的TCP协议会自动解析客户端发来的数据包,从中获取客户端的IP和端口号
c. addrlen表示第二个参数addr的大小
这里如果服务器应用层需要用到客户端的 IP和端口号,可以给accept指定第二个参数addr,以获取TCP链接时的客户端ip和端口号;如果服务器应用层不需要,则写NULL即可
addr的结构体类型为 struct sockaddr,在listen函数详解中我们有介绍过,由于这个结构体用起来不是非常方便,我们需要定义struct sockaddr_in结构体来使得sockaddr结构体操作更为便捷。
send()函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 作用:用于把数据通过socket发送给对端。不论是客户端还是服务端,应用程序都用send函数来向TCP连接的另一端发送数据。
参数说明
- sockfd:已建立好连接的socket。
- buf:需要发送的数据内存地址,可以是C语言基本数据类型变量的地址,也可以是数组、结构体、字符串、内存中有什么就发什么。
- len:需要发送的数据长度,为buf中的有效数据长度。
- flags填0,其他参数意义不大。
函数返回已发送的字符数,出错时返回-1,错误信息errno被标记。
注意:就算是网络断开,或socket已被对端关闭,send函数不会立即报错,要过几秒才会报错。
如果send函数返回的错误(<=0),表示通信链路已不可用。
recv()函数
int recv( SOCKET s, char *buf, int len, int flags);
- 作用:recv函数用于接受对端通过socket发送过来的数据。不论是客户端还是服务端,应用程序都用recv函数接收来自TCP连接的另一端发送过来数据。
参数说明
- sockfd:已建立好连接的socket。
- buf:用于接受的数据内存地址,可以是C语言基本数据类型变量的地址,也可以是数组、结构体、字符串,只要是一块内存就好。
- len:需要接收的数据长度,不能超过buf的大小,否则内存溢出。
- flags填0,其他参数意义不大。
函数返回已接受的字符数。出错时返回-1,失败时不会设置errno的值。
如果socket的对端没有发送数据,recv函数就会等待,如果对端发送了数据,函数返回接收到的字符数。出错时返回-1。如果socket对端关闭,返回值为0。
如果recv函数返回的错误(<=0),表示通信通道已不可用。