套接字的概念
套接字(socket)这个词在Tcp/Ip协议中,“IP地址”+ “TCP或UDP端口号”唯一标识网络通信中的一个进程,这个“ip地址”+“端口号”就称为socket.
预备知识
1.在实现我们的服务器之前我们需要 了解一些预备知识。
我们都知道主机有大端和小端之分,那么内存之中存储的数据也就有了大端和小端之分,而通讯双方的主机之间发送数据时因为大端和小端的缘故,发送和接受都需对数据的存储做相应的修改,非常不方便,为了使我们的网络程序有更好的移植性,我们调用以下的库函数做网络字节流和主机节序的转换。
h 表示 host n 表示 network l表示32位长整形 s表示16位短整形
利用这些库函数做相应的大小端转换后返回。
2.socket地址的数据类型。
socket数据结构
由图我们也可以看出来各种socket地址结构的开头是相同的,前16为表示结构体的长度,后16位表示地址类型,因此socket可以接受各种类型的socketaddr结构体指针做参数,但是这些函数的参数都用stuct socket *类型表示,使用前需要做强制转换。
3.相关函数
1)字符串转in_addr的函数:
对于IP地址,存放在结构体sockaddr_in中的成员struct in_addr. sin_addr 表示32位的IP地址。 而我们通常习惯性的用点分十进制的字符串表示IP地址。 所以需要将点分十进制的IP字符串转换为整形
2)创建套接字(获得一个文件描述符)
对于server和client来说,都需要创建一个套接字,来标识自己主机的IP地址及端口号,一对套接字代表一个链接。
第一个参数:IPV4协议 使用 AF_INET.
第二个参数:Tcp 协议 使用 SOCK_STREAM(面向字节流传输协议)
Udp 协议 使用 SOCK_DGRAM(面向数据报传输协议)
第三个参数:默认0
返回值:调用成功返回一个文件描述符,失败返回-1
3)Bind绑定主机和端口号
将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
注意:
这里的网络地址可以是INADDR_ANY (0),这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址。
因为服务器程序所监听的网络地址和端口号一般通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接。 因此服务器需要调用bind()绑定一个固定的网络地址和端口号。
而由于客户端不需要固定的端口号,因此不需要绑定,客户端的端口号是由内核自动分配的。
4)listen 监听
第二个参数:表示需要监听的个数。
注意的是客户端不需要监听,只有服务器才需要监听。
返回值:成功返回0, 失败返回-1.
5)accept 接受请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
第一个参数:套接字的文件描述符。
返回值:成功返回客户端的ip地址和端口号。
在客户端未连接上的时候,在tcp三次握手之后阻塞式等待。
6)connect 请求链接
是客户端向服务器发起连接请求的函数,
第一个参数:套接字的文件描述符。
第二个参数:服务器端的ip地址和端口号。
成功返回0, 失败返回-1;
4.演示代码
(1)服务器端server.c
1 #include <stdio.h>
2 #include <netinet/in.h>
3 #include <arpa/inet.h>
4 #include <sys/socket.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <errno.h>
8 #include <sys/types.h>
9
10 static void Usage(char* proc)
11 {
12 printf("%s[local_ip], [local_port]\n", proc);
13 }
14
15 int startup(const char* _ip, int _port)
16 {
17 int sock = socket(AF_INET, SOCK_STREAM, 0);
18 if(sock < 0)
19 {
20 perror("socket");
21 exit(5);
22 }
23 struct sockaddr_in local;//初始化协议地址
24 local.sin_family = AF_INET;
25 local.sin_port = htons(_port);
26 local.sin_addr.s_addr = inet_addr(_ip);
27
28 //将套接字和tcp服务绑定(服务端ip地址)
29 if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
30 {
31 perror("bind");
32 exit(3);
33 }
34 //监听这个套接字,监听指定端口,第二个参数表示可以排队连接的最大个数
35 if(listen(sock, 5) < 0)
36 {
37 perror("listen");
38 exit(4);
39 }
40 return sock;
41 }
42
43 //argv[]指针数组,指向各个参数
44 int main(int argc, char* argv[])
45 {
46 if(argc != 3)
47 {
48 Usage(ar