1、介绍:
socket是进程间的方式之一,是进程间的通信。这里说的进程并不一定是在同一台机器上也有可能是通过网络连接的不同机器上。只要他们之间建立了socket连接,那么数据便可以在机器之间进行双向的交流,直到连接断开为止。
2、socket的建立:
在我们接触实际的代码API之前,我们应该对基础的连接方式有所了解。
1
2
3
4
5
6
7
|
NOTE left of server:建立一个正在被监听的socket并等待客户端的连接 NOTE right of client:建立一个客户端socket并尝试连接server NOTE left of server:接受来自client的连接请求 server->client:发送与接受数据 client->server:接受与发送数据 NOTE left of server:关闭当前连接 NOTE right of client:关闭当前连接 |
上面就是基础的连接方式。
1、首先server需要创建正在被监听的socket,等待client连接请求。
2、client创建一个socket,尝试连接server。
3、server接受client的请求,建立起来两者之间的连接。
4、数据交换,双向通信
5、任何一方都可以断开连接,断开连接之后会自动销毁。
对于客户端来说:
1、通过系统函数socket()创建一个socket
2、通过系统函数connect()向server端口socket发起请求
3、交换数据,实现这种数据交换的方式有很多种,其中最简单的就是使用系统函数read(),write()
对于服务端来说:
1、通过系统调用函数sockcet()创建一个socket。
2、通过系统函数bind()绑定到这个socket到server的一个端口上。
3、通过系统函数listen()监听这个socket
4、当监听到有一个请求来临时,通过系统函数accept()接受一个请求。这个函数会阻塞io直到两者的连接完全断开。
5、交换数据
socket的类型:
当一个socket被建立起来时,进程间需要去说明所使用的协议和socket_type。只有通信双方都拥有相同的type和协议。
目前广泛实用的协议有大类,分别是unix文件系统协议,internet网络协议。对应的他们有各自的特点。使用unix_domain的双方使用公共的文件系统进行通信,使用internet_domain的进程分别位于不同的主机上,他们通过网络进行通信。
使用unix_domain的socket地址本质上就是文件系统的一个记录,本身是一条字符串。
使用internrt_domain的socket包含两个部分,一部分是主机的ip地址,一部分是socket绑定到的端口号。一般端口号比较低的端口都会被当作特殊的用途,比如端口号是80的端口是提供http服务的。
目前广泛实用的socket类型有两种,一种是流socket,一种是数据报socket。stream_socket处理通信就像是处理流水一样的连续不断的字节流,而datagram_sockets需要读取完整的字符,通常一个字符有几个字节组成。
接下来的内容是建立在使用TCP协议的基础上,这是一种可靠的面向流字节的协议,另外一种协议是UDP协议,这是一种不可靠的面向字符的协议。
client端的简单实例:
创建socket:
不管是server还是client,第一步都是创建socket:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# include <stdio.h> # include <stdlib.h> # include <sys/socket.h> # include <sys/types.h> int main( int argc, char * argv[]) { int socket_desc; socket_desc = socket(AF_INET,SOCK_STREAM, 0 ); if (- 1 ==socket_desc) { perror( "cannot create socket!
" ); exit( 1 ); } } |
socket()函数创建一个socket并且返回一个对应的描述符。
其对应的参数分别为:
AF_INET->ipv4
SOCK_STREAM->流socket
PROCOTOL 协议,使用ip协议
发起连接:
我们通过ip地址和端口号去连接远程主机,为此我们需要创建正确的结构体去保存远程主机的基本信息,从而表示远程主机。
1
|
struct sockaddr_in server; |
sockaddr_in 是一个包含网络地址的结构体,下面的是定义:
1
2
3
4
5
6
7
8
9
10
11
|
struct sockaddr_in { short sin_family' unsigned short sin_[ort; struct in_addr sin_addr; char sin_zero[ 8 ]; }; struct in_addr { unsigned long s_addr; }; |
可以看得到,这个结构体中还有一种类型为in_addr,其内部1的结构知识一个long类型的数据。ip地址便保存在这个long类型中。
函数inet_addr()可以很方便的将ip地址转换为long类型的格式。
1
|
server.sin_addr.s_addr = inet_addr( "127.0.0.1" ); |
既然知道了这个远程主机的server地址,那么接下来的就是发起连接了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# include <stdio.h> # include <stdlib.h> # include <sys/socket.h> #inlcude<sys/types.h> # include <netinet/ in .h> # include <arpa/inet.h> int main() { int socket_desc; struct sockaddr_in server; //创建socket socket_desc= socket(AF_INET,SOCK_STREAM, 0 ); if (- 1 ==socket_desc) { perror( "cannort cerate socket" ); exit( 1 ); } //设置远程服务器的信息 server.sin_family=AF_INER; server.sin_port=htons( 80 ); server.sin_addr.s_addr = inet_addr( "127.0.0.1" ); //连接: if (connect(socket_desc,(struct sockaddr*)&server,sizeof(server))< 0 ) { perror( "counld not connect!
" ); return 0 ; } //当服务器接受链接是便会建立连接 printf( "connct success
" ); return 0 ; } |
connect()函数回想服务器发起请求建立一个连接。
其参数为:
1、int sockfd =>socket 描述符
2、const struct sockaddr* addr =>sockaddr 的结构体,通用的socket地址。
3、socklen_taddrlen =>socket描述符的长度。
struct sockaddr 是通用的套接字地址,而struct sockaddr_in 则是internet环境下套接字地执形式,二者长度一样都是16个字节。二者是并列结构,指向sockaddr_in 结构的指针也可以指向sockaddr。一般情况下,需要把sockaddr_in 结构强制转换成sockaddr结构在传入系统调用函数中。更多见connect()。
另外,代码中htons()函数的作用是将主机的数据转化为网络字节序,至于为真么要转换数据的字节顺序,这里就先不说了,可以自己去了解。
到了这里,我们不仅创建了socket而且已经成功的连接了服务器。下面就是向服务器通信的过程了。
在socket上发送数据:
函数send()实现发送数据的功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# include <stdio.h> # include <string.h> # include <stdlib.h> # include <sys/socket.h> # include <arpa/inet.h> # include <netinet/ in .h> int main() { int sock_desc; struct sockaddr_in server; char *message; sock_desc=socket(AF_INET,SOCK_STREAM, 0 ); if (- 1 ==sock_desc) { perror( "cannot create socket
" ); exit( 1 ); } server.sinn_family=AF_INET; server.sin_port=htons( 80 ); server.sin_addr.s_addr=inet_addr( "127.0.0.1" ); if (connect(sock_desc,(struct sockaddr*)&server,sizeof(server))< 0 ) { perror( "connect error
" ); return 1 ; } printf( "connect successed
" ); message= "hello world
" ; if (send(sock_desc,message,strlen(message), 0 )< 0 ) { printf( "send error
" ); return 2 ; } printf( "message send success
" ); return 0 ; } |
send()函数的实现是向服务器发送数据,他其实就是向socket写数据,类似的就像是向文件中写入数据。
其参数为:
1、int sockfd=>指定发送数据的socket描述符
2、const void * buff=>发送数据
3、size_t nbytes=>发送数据的长度
4、int flags=>标志
到现在为止,我们已经完成了client的大部分操作,已经能够向对方发送数据,那么接下来就是接受服务端的返回的数据了。
通过socket接收数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
# include <stdio.h> # include <string.h> # include <stdlib.h> # include <sys/socket.h> # include <arpa/inet.h> # include <netinet/ in .h> int main() { int sock_desc; struct sockaddr_in server; char *message; sock_desc=socket(AF_INET,SOCK_STREAM, 0 ); if (- 1 ==sock_desc) { perror( "cannot create socket
" ); exit( 1 ); } server.sinn_family=AF_INET; server.sin_port=htons( 80 ); server.sin_addr.s_addr=inet_addr( "127.0.0.1" ); if (connect(sock_desc,(struct sockaddr*)&server,sizeof(server))< 0 ) { perror( "connect error
" ); return 1 ; } printf( "connect successed
" ); message= "hello world
" ; if (send(sock_desc,message,strlen(message), 0 )< 0 ) { printf( "send error
" ); return 2 ; } printf( "message send success
" ); //接收数据 if (recv(sock_desc,server_reply, 2000 , 0 )< 0 ) { perror( "recv failed
" ); return 3 ; } printf( "recv seccessed
" ); puts(server_reply); return 0 ; } |
recv()函数就是为了接受socket的数据,其参数为:
1、int sockfd=>接收端的socket描述符
2、void * buff => 存放数据的缓冲区,数据存放在* buff中。
3、size_t nbytes=> 知名buff的长度。
4、int flags=> 一般设置为0
至此,我们完成了,socket通信的主要流程,成功的拿到了服务端返回的数据,在上方通信完成之后,便可以将此socket连接关闭。
关闭socket:
close(sock_desc);
client端口的总结:
所以通过上面的讨论,最终的全部代码是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
#inlcude<stdio.h> # include <string.h> # include <stdlib.h> # include <sys/types.h> # include <sys/socket.h> # include <arpa/inet.h> # include <netinet/ in .h> #Include<unistd.h> int main() { int socket_desc; struct sockaddr_in server; char * message,server_reply[ 2000 ]; //创建socket socket_desc = socket(AF_INET,SOCK_STREAM, 0 ); if (- 1 ==socket_desc) { perror( "socket failed
" ); exit( 1 ); } server.sin_family=AF_INET; server.sin_port=htons( 80 ); server.sin_addr.s_addr=inet_addr( "127.0.0.1" ); //进行连接: if (connect(socket_desc,(struct sockaddr*)&server,sizeof(server))< 0 ) { perror( "connect connect
" ); return 1 ; } //发送数据: messgge= "hello world
" ; if (send(socket_desc,message,strlen(message), 0 )< 0 ) { perror( "send dagta error
" ); return 2 ; } printf( "send message successed
" ); //接收数据 if (recv(socket_addr,server_reply, 2000 , 0 )< 0 ) { perror( "recv success
" ); return 3 ; } printf( "recv success
" ); puts(server_reply); //关闭socket close(socket_desc); return 0 ; } |