TCP客户/服务器模型
1.客户端调用序列
客户端编程序列如下:
-
调用socket函数创建套接字
-
调用connect连接服务器端
-
调用I/O函数(read/write)与服务器端通讯
-
调用close关闭套接字
2.服务器端调用序列
服务端的编程序列如下:
-
调用socket函数创建套接字
-
调用bind绑定本地地址和端口
-
调用listen启动监听
-
调用accept从已连接队列中提取客户端连接
-
调用I/O函数(read/write)与客户端通讯
-
调用close函数关闭套接字
回射客户/服务器
socket函数
包含头文件 <sys/socket.h>
功能:创建一个套接字用于通信(socket - create an endpoint for communication)
原型
int socket(int domain, int type, int protocol);
参数
domain:指定通信协议族(protocol family)
type:指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol:协议类型
返回值:成功返回非负整数,它与文件描述符类似,我们把它称为套接字描述字,简称套接字。失败返回 -1
拓展:
域参数指定一个通信域;这将选择用于通信的协议族。这些族在<sys/socket.h>中定义。(The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. These families are defined in <sys/socket.h>.)
domain:
目前了解的格式包括:(The currently understood formats include:)
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk ddp(7)
AF_PACKET Low level packet interface packet(7)
type:
套接字具有指定的类型,该类型指定通信语义。目前定义的类型有:(The socket has the indicated type, which specifies the communication semantics. Currently defined types are:)
SOCK_STREAM 提供有序、可靠、双向、基于连接的字节流。可能支持带外数据传输机制。(Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.)
SOCK_DGRAM 支持数据报(无连接,不可靠的固定最大长度的消息)。(Supports datagrams (connectionless, unreliable messages of a fixed maximum length).)
SOCK_SEQPACKET 为最大长度固定的数据报提供有序、可靠、基于双向连接的数据传输路径;消费者需要在每次输入系统调用时读取整个数据包。(Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read anentire packet with each input system call.)
SOCK_RAW 提供原始网络协议访问。(Provides raw network protocol access.)
SOCK_RDM 提供不保证排序的可靠数据报层。(Provides a reliable datagram layer that does not guarantee ordering.)
SOCK_PACKET 过时的,不应用于新项目;参考手册(7)。( Obsolete and should not be used in new programs; see packet(7).)
某些套接字类型可能不能由所有协议族实现;例如,SOCK_SEQPACKET没有在AF_INET中实现。(Some socket types may not be implemented by all protocol families; for example, SOCK_SEQPACKET is not implemented for AF_INET.)
由于Linux 2.6.27,类型参数有第二个用途:除了指定套接字类型外,它还可以包括按位或下列任何一个值,以修改socket()的行为:
(Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any of the following values, to modify the behavior of socket():)
SOCK_NONBLOCK 在新打开的文件描述中设置O_NONBLOCK文件状态标志。使用这个标志可以节省对fcntl(2)的额外调用,从而获得相同的结果。( Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to achieve the same result.)
SOCK_CLOEXEC 在新的文件描述符上设置close-on-exec (FD_CLOEXEC)标志。查看open(2)中关于O_CLOEXEC标志的说明,了解为什么可能使用它的原因‐。( Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be use‐ful.)
bind函数
1 #include <sys/types.h> /* See NOTES */
2 #include <sys/socket.h>
3 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
5 @param sockfd : 套接字描述符
6 @param addr : 通用套接字地址结构的地址
7 @param addrlen : 套接字地址结构的长度
8
9 @return
10 成功返回 0
11 失败返回 -1
bind() 函数用于绑定一个固定的 ip 地址与端口号,为客户端提供可以访问的地址,相应的ip 地址与端口号在通用地址结构中存储.
struct sockaddr 结构体为通用地址结构,主要用于存储 ip 地址与端口号
1 struct sockaddr {
2 sa_family_t sa_family;
3 char sa_data[14];
4 };
5 @sa_family : 地址族 AF_INET
6 @sa_data : ip地址与端口号
- 由于通用地址结构 ip 地址与端口号保存在一个数组中,不便于区分,在 Internet 协议族重新设计了地址结构,这个地址结构为 struct sockaddr_in
- ip 地址 与端口号使用单独的成员来表示
- 同时为了兼容 bind() 函数接口,使用了数组进行填充,保持和原结构一致
1 struct sockaddr_in {
2 __kernel_sa_family_t sin_family; /* 地址族*/
3 __be16 sin_port; /* 端口号 */
4 struct in_addr sin_addr; /* ip 地址*/
5 /* 填充数组,保持与 struct sockaddr 结构大小一致 */
6 unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
7 };
listen、
流 socket 通常可以分为主动和被动两种:在默认情况下,使用 socket() 创建的 socket 是主动的,一个主动的 socket 可用在connect() 调用中来建立一个到一个被动 socket 的连接.一个被动 socket 是一个通过调用 listen() 以被标记成允许接入连接的 socket.在大多数使用流 socket 的应用场景,服务器会执行被动式打开,而客户端会执行主动式
listen() 函数的原型如下:
1 #include <sys/types.h> /* See NOTES */
2 #include <sys/socket.h>
3 int listen(int sockfd, int backlog);
4 @param sockfd : socket 文件描述符
5 @param backlog : 未决连接的数量
accept、
accept() 系统调用在文件描述符 sockfd 引用的监听流 socket 上接受一个接入连接,如果在调用 accept() 时不存在未决的连接,那么调用就会阻塞直到有连接请求到达为止
accpet() 函数的原型为:
1 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
2 @param sockfd : 套接字文件描述符
3 @param addr : 客户端地址结构对象指针
4 @param addrlen :地址结构长度指针
5
6 @return
7 成功 : 返回新的文件描述符
connect、
connect() 函数将文件描述符 sockfd 引用的主动 socket 连接到地址通过 addr 和 addrlen 指定的监听 socket 上
1 int connect(int sockfd, const struct sockaddr *addr,
2 socklen_t addrlen);
3 @param sockfd : 文件描述符
4 @param addr : 服务器地址结构对象的指针
5 @param addrlen :地址结构的长度
6
7 @return :
8 成功 : 返回 0
9 失败 : 返回 -1
server.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <error.h>
5
6 #include <sys/socket.h>
7 #include <sys/types.h>
8 #include <arpa/inet.h>
9 //#include <sys/un.h>
10
11 #define handle_error(message) \
12 do \
13 { \
14 perror(message); \
15 exit(EXIT_FAILURE); \
16 } while(0);
17
18 int getSocketFd()
19 {
20 int socket_listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
21 if (socket_listenfd < 0)
22 handle_error("socket");
23
24 return socket_listenfd;
25 }
26
27 void bindSocketFd(int socket_listenfd)
28 {
29 struct sockaddr_in serverAddr;
30 memset(&serverAddr, 0, sizeof(serverAddr));
31 serverAddr.sin_family = PF_INET;
32 serverAddr.sin_port = htons(7777);
33 serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
34 /*serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
35 /*inet_ntoa("127.0.0.1", &serverAddr.sin_addr);*/
36
37 if (bind(socket_listenfd, (struct sockaddr * )&serverAddr, sizeof(serverAddr)) < 0)
38 handle_error("bind");
39
40 if(listen(socket_listenfd, SOMAXCONN) < 0)
41 handle_error("listen");
42
43 struct sockaddr_in clientAddr;
44 memset(&clientAddr, 0, sizeof(clientAddr));
45 int lengthOfClientAddr = sizeof(clientAddr);
46 int connectFd = 0;
47 if((connectFd = accept(socket_listenfd, (struct sockaddr * )&clientAddr, &lengthOfClientAddr)) < 0)
48 handle_error("acept");
49
50 char recvBuf[1024];
51 while (1)
52 {
53 memset(recvBuf, 0, sizeof(recvBuf));
54 int ret = read(connectFd, recvBuf, sizeof(recvBuf));
55
56 fputs(recvBuf, stdout);
57 write(connectFd, recvBuf, ret);
58 }
59
60 close(socket_listenfd);
61
62 }
63
64 int main(int argv, char * argc[])
65 {
66 int socket_listenfd = getSocketFd();
67
68 bindSocketFd(socket_listenfd);
69
70 return 0;
71 }
client.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <error.h>
6
7 #include <sys/types.h>
8 #include <sys/socket.h>
9 #include <arpa/inet.h>
10 #include <netinet/in.h>
11
12 #define handle_error(message) \
13 do \
14 { \
15 perror(message); \
16 exit(EXIT_FAILURE); \
17 } while(0);
18
19 void clientToServer()
20 {
21 int clientSockFd;
22 if((clientSockFd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
23 handle_error("client socket");
24
25 struct sockaddr_in clientAddr;
26 memset(&clientAddr, 0, sizeof(clientAddr));
27 clientAddr.sin_family = PF_INET;
28 clientAddr.sin_port = htons(7777);
29 clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
30
31 if(connect(clientSockFd, (struct sockaddr * )&clientAddr, sizeof(clientAddr)) < 0)
32 handle_error("client connect");
33
34 char sendBuf[1024] = {0};
35 char recvBuf[1024] = {0};
36
37 while (fgets(sendBuf, sizeof(sendBuf), stdin) != NULL)
38 {
39 write(clientSockFd, sendBuf, sizeof(sendBuf));
40 read(clientSockFd, recvBuf, sizeof(recvBuf));
41
42 fputs(recvBuf, stdout);
43 memset(sendBuf, 0, sizeof(sendBuf));
44 memset(recvBuf, 0, sizeof(recvBuf));
45 }
46
47 close(clientSockFd);
48 }
49
50 int main(int argv, char * argc[])
51 {
52 clientToServer();
53
54 return 0;
55 }