socket套接字编程
一、socket概念
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现, socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭). Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议,所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
二、socket通信过程
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
三、socket函数介绍
int socket(int protofamily, int type, int protocol);//返回sockfd
sockfd是描述符。
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()函数把一个地址族中的特定地址赋给socket
函数的三个参数分别为:
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同
小技巧:用man 7ip迅速查找到并粘贴出来
addrlen:对应的是地址的长度。
注意:这个函数是服务器独有的,客户端不需要,因为客户端在调用connect函数的时候系统会自动分配一个本机ip+端口给他。
网络字节序与主机字节序
主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。
int listen(intsocketfd,intbacklog);
此函数调用后,当客户端调用connect函数发出连接请求时,服务器端会收到此请求。
且listen函数一旦调用,此fd将变成被动套接字(今后只能等待别人来连接,而不能主动连)
内部维护了两个队列:1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手
2、已完成连接的队列
后续调用的accept函数(继续往后看)会从第二个队列中取出一个连接
●socketfd:就是那个fd
●backlog:排队的最大连接个数
●返回值:0成功,-1失败
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
将客户端连接到服务器,调用connect函数后服务器的accept函数会收到这个连接的
●sockfd:就是那个fd(客户端的代言人)
●addr:要连接的服务器的地址(在调用connect之前要填充这个地址的结构体!)
●addrlen:地址长度
●返回值:成功返回0,失败返回-1
int accept(int sockfd, structsockaddr *addr, socklen_t *addrlen);
参数sockfd
参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
参数addr
这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
参数len
如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
●返回值:成功返回客户端的fd(客户端),失败返回-1
如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。
读写函数
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
#include
wz@wz-machine:~/linux/socket/1$ cat utili.h
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define SERVER_PORT 9090
#define SERVER_IP "127.0.0.1"
#define LISTEN_QUEUE 5
#define BUFFER_SIZE 254
Ser.c
1 #include"utili.h"
2
3 int main()
4 {
5 //创建socket
6 int Socket_Ser = socket(AF_INET,SOCK_STREAM,0);
7 if(Socket_Ser == -1)
8 {
9 printf("Socket_ser error\n");
10 return -1;
11 }
12
13 //协议地址
14 struct sockaddr_in addrSer,addrCli;
15 addrSer.sin_family = AF_INET;
16 addrSer.sin_port = htons(SERVER_PORT);
17 addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
18 if( bind(Socket_Ser, (struct sockaddr*)&addrSer, sizeof(addrSer)) == -1)
19 {
20 printf("bind socket error\n");
21 exit(0);
22 }
23 int res = listen(Socket_Ser,LISTEN_QUEUE);
24 if(res == -1)
25 {
26 printf("listen error\n");
27 return -1;
28 }
29 int SockConn;
30 socklen_t len = sizeof(struct sockaddr);
31 SockConn = accept(Socket_Ser,(struct sockaddr*)&addrCli,&len);
32 if(SockConn == -1)
33 {
34 printf("SocketConnect error\n");
35 return -1;
36 }
37 else
38 {
39 printf("Server accept Client connect ok\n");
40 printf("Client IP:>%s\n",inet_ntoa(addrCli.sin_addr));
41 printf("Client PORT:>%d\n",ntohs(addrCli.sin_port));
42 }
43
44 char sendbuf[BUFFER_SIZE];
45 char recvbuf[BUFFER_SIZE];
46
47 while(1)
48 {
49 printf("Ser:>");
50 scanf("%s",sendbuf);
51 send(SockConn,sendbuf,strlen(sendbuf)+1,0);
52
53 recv(SockConn,recvbuf,BUFFER_SIZE,0);
54 printf("Cli:>%s\n",recvbuf);
55 }
56 close(Socket_Ser);
57 return 0;
58 }
59
Cli.c
1 #include"utili.h"
2
3 int main()
4 {
5 int sockCli = socket(AF_INET,SOCK_STREAM,0);
6
7 struct sockaddr_in addrSer;
8 addrSer.sin_family = AF_INET;
9 addrSer.sin_port = htons(SERVER_PORT);
10 addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
11
12 struct sockaddr_in addrCli;
13 addrCli.sin_family = AF_INET;
14 addrCli.sin_port = htons(7070);
15 addrCli.sin_addr.s_addr = inet_addr("192.168.3.252");
16
17
18 socklen_t len = sizeof(struct sockaddr);
19 bind(sockCli, (struct sockaddr*)&addrCli, len);
20
21 int res = connect(sockCli,(struct sockaddr*)&addrSer,len);
22 if(res == -1)
23 {
24 printf("Client connet Server FAIL\n");
25 return -1;
26 }
27 else
28 {
29 printf("Client connect Server OK\n");
30 }
31
32 char sendbuf[BUFFER_SIZE];
33 char recvbuf[BUFFER_SIZE];
34
35 while(1)
36 {
37 recv(sockCli,recvbuf,BUFFER_SIZE,0);
38 printf("Ser:>%s\n",recvbuf);
39
40 printf("Cli:>");
41 scanf("%s",sendbuf);
42 send(sockCli,sendbuf,strlen(sendbuf)+1,0);
43 }
44 close(sockCli);
45 return 0;
46 }
结果:
wz@wz-machine:~/linux/socket/1$ ./ser
Server accept Client connect ok
Client IP:>127.0.0.1
Client PORT:>46802
Ser:>hello
Cli:>world
wz@wz-machine:~/linux/socket/1$ ./cli
Client connect Server OK
Ser:>hello
Cli:>world
Ser:>hello
Cli:>^C