01 Linux网络编程基础——服务端与客户端的基本模型

本文介绍了Linux下服务端与客户端的基本网络编程模型,涉及socket、setsockopt、bind、listen、accept等关键步骤。在编程中,特别注意服务端处理客户端数据返回的逻辑,以及send、write、recv、read函数在不同情况下的返回值。文章还探讨了TCP连接中的MSL等待时间、端口复用以及bind和accept的细节。同时,给出了服务端和客户端的代码示例。

在编程时需注意:

  1. 此模型的服务器是将客户端发送过来的数据进行返回,所以客户端的buf不能在读取数据后清零。这样会使得发送回客户端的数据为空,即发送了零个字节给客户端,但是这样依然是可以发送成功的,即ret == 0;但是在客户端,就会因没有读取服务端的数据(0个字节)而阻塞在send函数处,从而出现在客户端发送数据时不做任何反应的情况。(找这个问题时相当苦恼...)
  2. write与send并不会因为发送的字节数为空而阻塞,这一点在上面也提到了。
  3. send、wirte、recv、read返回0也有可能是:
    1. 客户端发送FIN,服务器无法读取数据,但是此时仍能够写入数据。
    2. 服务端发送FIN,客户端无法发送和读取数据,其实在发送FIN后就无法操作了。
    3. 当客户端Ctrl + c时,服务端读数据ret = 9,写数据ret = 0。
  4. 如果客户端向服务器发送0个字节而不做拦截的话,那么就会因为读不到字节而阻塞在服务器的读函数处。
  5. 可以使用errno == EBADF来区分具体的退出原因。EBADF表示无效的文件描述符。可以在man手册对recv函数的描述中查道:“The argument sockfd is an invalid descriptor.”。


----------------------------------------------------------------------------2019.2.2--------------------------------------------------------------------------------

一.服务端:socket -> setsockopt ->bind -> listen -> accept

socket 表示打开一个网络通讯端口。函数原型如下:
int socket(int domain, int type, int protocol);

1.域参数domain可以是:AF与PF在功能上并没有什么区别,它们所代表的值都是相同的。BSD,是AF,对于POSIX是PF。只不过AF一般指定地址(Address Family),而PF强调指定协议(Protocol Family)。
        TCP:AF_INET / PF_INET
        UDP:PF_UNIX / PF_LOCAL / AF_UNIX / AF_LOCAL

2.type:
        TCP:SOCK_STREAM
        UDP:SOCK_DGRAM

3.protocol:传0表示使用默认的协议。

 

setsockopt 依据tcp协议,主动断开连接的一方,会有2MSL(Maximum Segment Lifetime)的等待时间,目的是确保它发送的表示收到对方FIN标志ACK送达。所以在Ctrl + c服务端的时候,就会有2MSL的等待时间,此时还在占用IP地址,重启服务器的话就会出现端口被占用,所以需要设置端口复用。

    //设置端口复用
    int on = 1;
    //第一个参数: sockfd 套接字
    //第二个参数: 级别 固定SOL_SOCKET
    //第三个参数: 选项名字 SO_REUSEADDR
    //第四个参数: int类型  on = 1表示开启
    //第五个参数: sizeof(int)
    //ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(int));
    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));
    if (-1 == ret)
    {
        perror("setsockopt"); 
        return 1; 
    }
    printf("设置端口复用成功....\n");

netstat:查看网络的状态信息。 常用:netstat -apn | grep server 

bind 将参数sockfd和addr绑定在一起。addr结构体中保存了协议类型IP端口号,而且它们也应该是被转换为网络序的。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

1.struct sockaddr *:是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同。

2.socklen_t addrlen:因此参数三需要指定长度。

struct sockaddr_in addr;
bzero(&addr, sizeof(addr));              //清零
addr.sin_family = AF_INET;                   //协议族
addr.sin_addr.s_addr = htonl(INADDR_ANY);    //绑定本地所有网卡
addr.sin_port = htons(6666);                 //端口号

listen 监听用于网络通讯的文件描述符。
int listen(int sockfd, int backlog);

backlog:排队建立3次握手队列和刚刚建立3次握手队列的链接数和。最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。
查看系统默认backlog:cat /proc/sys/net/ipv4/tcp_max_syn_backlog

accept 三次握手完成之后服务器调用accetp接受连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

1.addr:用于接收客户端信息的struct sockaddr结构体。

2.addrlen:是一个传入传出参数(value-result argument)。
        传入:指定调用者提供的缓冲区addr的大小,避免造成缓冲区溢出。
        传出:传出的是客户端结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给addr参数传NULL,表示不关心客户端的信息。

二.客户端 socket -> connect

socket 同上,此处不多加赘述。

connfd  客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

#include <sys/types.h> 					/* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 

 

贴出服务端和客户端的代码:

server.c

/* ************************************************************************
 *       Filename:  server.c
 *    Description:  
 *        Version:  1.0
 *        Created:  2018年12月25日 00时02分59秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:  YOUR NAME (), 
 *        Company:  
 * ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/errno.h>

#define SIZE 128

int main(void)
{
	int ret = -1;
	int sockfd = -1;
	int connfd = -1;
        int on = 0;

	struct sockaddr_in sockaddr;

	char buf[SIZE];
		
	//1.create sockfd
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd) {
		perror("socket");
		goto err2;
	}
    
        //设置端口复用
        on = 1;
        ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));
        if (-1 == ret)
        {
            perror("setsockopt"); 
            return 1; 
        }
        printf("设置端口复用成功....\n");

	//2.bind info
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(10086);
	sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	ret = bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
	if (-1 == ret) {
		perror("bind");
		goto err1;
	}

	//3.listen the sockfd
	ret = listen(sockfd, 10);
	if (-1 == ret) {
		perror("listen");
		goto err1;
	}
	printf("the server is listenning now...\n");
	
	//4.accept
	connfd = accept(sockfd, NULL, NULL);
	if (-1 == connfd) {
		perror("accept");
		goto err1;
	}
	printf("accept successed! : %d\n", connfd);
	ret = send(connfd, buf, 0, 0);
	if (-1 == ret || 0 == ret) {
		printf("ret = %d\n", ret);
	}
	//5.
	while (1) {
		memset(buf, 0, SIZE);
		ret = recv(connfd, buf, SIZE, 0);
		if (-1 == ret || 0 == ret) {
			printf("读数据\n");
			if (errno == EBADF) {
				perror("recv");
				break;
			}
		}
		printf("----> ret:%d %s\n", ret, buf);

		ret = send(connfd, buf, strlen(buf), 0);
		if (-1 == ret || 0 == ret) {
			printf("写数据\n");		
			perror("send");
			break;
		}
	}
	return 0;
err1:
	close(sockfd);
	return 1;
err2:
	return 1;
}

client.c

/* ************************************************************************
 *       Filename:  client.c
 *    Description:  
 *        Version:  1.0
 *        Created:  2018年12月24日 23时33分28秒
 *       Revision:  none
 *       Compiler:  gcc
 *         Author:  YOUR NAME (), 
 *        Company:  
 * ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/errno.h>

#include <sys/socket.h>

#define SIZE 128

int main(void)
{
	int ret = -1;
	int sockfd = -1;
	struct sockaddr_in sockaddr;	
	
	char buf[SIZE];

	//1.create a sockfd
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	
	memset(&sockaddr, 0, sizeof(sockaddr));
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(10086);
	ret = inet_pton(AF_INET, "192.168.43.69", &sockaddr.sin_addr);
	if (-1 == ret) {
		perror("inet_pton");
		goto err1;
	}
	
	ret = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
	if (-1 == ret) {
		perror("connect");
		goto err1;
	}
	printf("connect server successed...\n");

	while (1) {
		memset(buf, 0, SIZE);
		fgets(buf, SIZE, stdin);
		if ('\n' == buf[strlen(buf) - 1])
			buf[strlen(buf) - 1] = 0;

		ret = send(sockfd, buf, strlen(buf), 0);
		if (ret == 0 || errno != EBADF)
			continue;
		if (ret <= 0) {
			printf("读数据\n");
			perror("send");
		}
		printf("send %d byte......\n", ret);
		
		ret = recv(sockfd, buf, SIZE, 0);
		if (-1 == ret || 0 == ret) {
			printf("写数据\n");
			perror("recv");
		}
		printf("ret = %d buf: %s\n", ret, buf);
	}

	printf("quit.........\n");
	close(sockfd);

	return 0;
err1:
	close(sockfd);
	return 1;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值