本节开始编写基于TCP的客户端和服务器连接通信程序

3.9.6.1、服务器端程序编写

(1)socket:打开一个文件描述符
(2)bind:让服务器和当前运行服务器的电脑IP绑定。做服务器的电脑必须有一个外网IP地址,做客户端的电脑可以没有,因为客户端可以通过路由器[路由器有外网IP地址]连接到Inet,然后通过inet找到外网IP,找到绑定服务器的电脑。然后通过端口号找到这台电脑的相对应的服务器[表现为一个服务器进程]---》[端口,协议,IP三者识别]
(3)listen
(4)accept,返回值是一个fd,accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。注意:socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。

3.9.6.2、客户端程序编写

(1)socket
(2)connect概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
1、创建一个server.c和一个client.c
2、服务器端程序编写:server.c

2.1 先socket打开文件描述符

返回的socket描述字存在于协议族空间中,但是没有一个具体的地址,此时内核还没有给socket分配源地址和源端口。但是它可以唯一标识一个socket。

2.2、使用bind绑定服务器和电脑IP&端口号客户端通过IP地址找到相应电脑,通过端口号找到对应服务器。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
* sockfd:要绑定的socket服务器的文件描述符,用socket函数获得.bind函数给这个socket描述字绑定一个名字
* addr要绑定给socket服务器的地址。

	* 通常服务器在启动的时候会绑定一个公开的地址[IP+端口号],用于提供服务,客户通过它来连接服务器,而客户端就不用指定,有系统自动分配一个端口号和自身的IP地址相组合。这就是为什么通常服务器在listen之前会调用bind,而客户端不会调用,而是connect时由系统随机生成一个。
	* 这里要注意使用的是IPv4还是IPv6,其对应的结构体不同。
	* IPV4对应struct sockaddr_in,IPV6对应struct sockaddr_in6,Unix对应struct sockaddr_un,这里我用的是IPV4,所以我定义了struct sockaddr_in seraddr。     
* 第三参数是第二个参数结构体的长度     

出现的错误:server.c:12:9: error: variable ‘seraddr’ has initializer but incomplete type struct sockaddr_in seraddr = {0};原因:没有包含相应头文件 教训:在调用某个函数之前要先把相应头文件加进来,再

写代码注意:在将一个地址绑定到socket的时候,一定要把主机字节序转换成网络字节序,或者会导致出现很多莫名其妙的问题。

2.3 listen监听端口6003[只要有客户端给端口6003发消息,就表示这个客户端要访问这个服务器]

include <sys/types.h> /* See NOTES */

include <sys/socket.h>

int listen(int sockfd, int backlog);
* sockfd参数是要监听的socket服务器的文件描述符
* backlog是可以监听的客户端队列,可以自由设置,主要看服务器的负载能力,我定义为100

如果服务器不能监听,就直接退出程序[因为服务器必须进入监听状态才能和客户端握手],表示出错。

* TCP服务器端依次调用socket,bind,listen之后,就可以监听指定的socket地址了。
* TCP客户端依次调用socket,connect之后就向TCP服务器发送了一个连接请求。
* TCP服务器监听到这个请求之后,就会调用accept函数接收请求,从而建立连接。

注意:添加头文件。

2.4 accept阻塞等待客户端接入

include <sys/types.h> /* See NOTES */

include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
* On error, -1 is returned, and errno is set appropriately[可以使用perror].
* 第2,3个参数,是指针类型的,而且没有加const修饰,表示这两个是输出型参数,用来返回客户端的IP地址和IP地址长度
* 因为要输出的第2个参数addr是IPv4,因此定义一个struct sockaddr_in cliaddr = {0}来接收客户端IP地址,定义一个输出socklen_t len来返回客户端的长度
* 如果没有接收到TCP客户端请求,就一直阻塞,如果accept成功,就返回一个由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
* 注意:accept的第一个参数为服务器的socket模式字,是服务器开始调用socket函数生成的,称为监听socket描述字;而accept函数返回的是已经连接的socket描述字。一个服务器通常仅仅只创建一个监听socket描述字,它在该服务器的声明周期内一直存在,当服务器完成了对某个客户的服务,相应的已经连接的socket描述字就被关闭

	* 即:socket返回监听sockfd,是服务器用来监听客户端的,不能用了读写;accept返回读写sockfd,是服务器用来和客户端进行读写操作的

3、客户端程序编写
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
* 第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度
* 客户端通过connect来建立与TCP服务器的连接。
如果作为一个服务器,在调用socket,bind之后就会调用listen来监听这个socket,如果客户端这时调用connect发出连接请求,服务器端就会接收这个请求。

include <stdio.h>#include <sys/socket.h>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <arpa/inet.h>#define SERADDR "192.168.129.128" // 服务器开放给我们的IP地址和端口号#define SERPORT 9003int main(void){ // 第1步:先socket打开文件描述符 int sockfd = -1, ret = -1, clifd = -1; struct sockaddr_in seraddr = {0}; struct sockaddr_in cliaddr = {0}; // 第1步:socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { perror("socket"); return -1; } printf("socketfd = %d.\n", sockfd); // 第2步:connect链接服务器 seraddr.sin_family = AF_INET; // 设置地址族为IPv4 seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息 seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址 clifd = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr)); if (clifd < 0) { perror("listen"); return -1; } printf("connect success, clifd = %d.\n", clifd); return 0;}4、测试4.1在Ubuntu的终端执行以下命令

* gcc cilent.c
* ./a.out

	* sockfd = 3.
	* connect: Connection refused
	* connect resule, ret = -1.

4.2 打开终端1,执行:
* gcc server.c -o ser
* sockfd = 3.
* bind success.
* 阻塞阻塞阻塞...

打开终端2,执行
* gcc cilent.c -o cli
* ./cli

	* sockfd = 3.
	* connect resule, ret = 0.
	* 同时终端1,不再阻塞,现象:

		* sockfd = 3.
		* bind success.
		* ret = 4.

4.3 写一个makefileall: gcc server.c -o ser gcc client.c -o cliclean: rm ser cli .o -rf此时服务器就和客户端建立了连接,接下来就可以进行双方通信了。注意:只要本地能够访问服务器,那么远程也能:远程访问服务器只是经过能Internet,Internet如何找到对应服务器与程序员无关。本地电脑测试和远程电脑测试是一样的。5、双方通信5.1 客户端发送&服务端接收5.1.1 基础代码客户端发送代码编写client.cssize_t send(int sockfd, const void buf, size_t len, int flags); //const void 输入型参数,char sendbuf[100];int main(void){** // 第2步:connect链接服务器******* printf("connect success, clifd = %d.\n", clifd); //此后就可以通信了。 strcpy(sendbuf, "hello world!"); ret = send(sockfd, sendbuf, strlen(sendbuf), 0); printf("成功发送了%d个字节.\n", ret); return 0;}服务器端接收发送的字符server.cssize_t recv(int sockfd, void buf, size_t len, int flags);char recvbuf[100];int main(void){*** //4. accept阻塞等待客户端接入,如果没有接收到就一直阻塞 clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); printf("clifd = %d.\n", clifd); //注意,这个clifd是第4步返回的读写fd,不是第一步socket创建的fd ret = recv(clifd, recvbuf, sizeof(recvbuf), 0); printf("成功接收了%d个字节.\n", ret); printf("发送过来的内容是:%s.\n", recvbuf); return 0;}调试
* make
* ./ser

	* sockfd = 3.
	* bind success.
	* clifd = 4.
	* 成功接收了12个字节.
	* 发送过来的内容是:hello world..
* 打开另外一个端口:./cli
* sockfd = 3.
* connect resule, ret = 0.
* 发送了12个字符

5.2 将基础代码屏蔽,改进代码。在client.c中: *************** while(1) { printf("请输入要发送的内容:"); scanf("%s", sendbuf); printf("刚才输入的是:%s.\n", sendbuf); } *************调试:
* make
* ./ser
* 打开另外一个端口:./cli

	* 成功
* 屏蔽printf

***** while(1) { printf("请输入要发送的内容:"); scanf("%s", sendbuf); //printf("刚才输入的是:%s", sendbuf); ret = send(sockfd, sendbuf, strlen(sendbuf), 0); printf("发送了%d个字符\n", ret); } ****在server.c中 while(1) { ret = recv(clifd, recvbuf, sizeof(recvbuf), 0); //printf("成功接收了%d个字节.\n", ret); printf("发送过来的内容是:%s.\n", recvbuf); memset(recvbuf, 0, sizeof(recvbuf)); }结果:实现客户端发送,服务器接收5.2 服务器发送&客户端接收5.2.1 基础代码server.c中 strcpy(recvbuf, "hello world."); ret = send(clifd, recvbuf, strlen(recvbuf), 0); printf("发送了%d个字符\n", ret);在client.c中 ret = recv(sockfd, sendbuf, sizeof(sendbuf), 0); printf("成功接收了%d个字节.\n", ret); printf("发送过来的内容是:%s.\n", sendbuf);
* make
* ./ser

	* bind后阻塞
* ./cli

	* 客户端一连接上服务器,就立刻收到了服务器发来的hello world.

提出问题:从上面可以看出,server和client之间通信是异步的[就是不知道对方会什么时候发消息过来],如果要解决这个问题,就要通过应用层协议来解决这个问题。5.3、添加应用层协议

posted on 2018-06-29 09:23  Ocean&Star  阅读(243)  评论(0编辑  收藏  举报