在编程时需注意:
- 此模型的服务器是将客户端发送过来的数据进行返回,所以客户端的buf不能在读取数据后清零。这样会使得发送回客户端的数据为空,即发送了零个字节给客户端,但是这样依然是可以发送成功的,即ret == 0;但是在客户端,就会因没有读取服务端的数据(0个字节)而阻塞在send函数处,从而出现在客户端发送数据时不做任何反应的情况。(找这个问题时相当苦恼...)
- write与send并不会因为发送的字节数为空而阻塞,这一点在上面也提到了。
- send、wirte、recv、read返回0也有可能是:
- 客户端发送FIN,服务器无法读取数据,但是此时仍能够写入数据。
- 服务端发送FIN,客户端无法发送和读取数据,其实在发送FIN后就无法操作了。
- 当客户端Ctrl + c时,服务端读数据ret = 9,写数据ret = 0。
- 如果客户端向服务器发送0个字节而不做拦截的话,那么就会因为读不到字节而阻塞在服务器的读函数处。
- 可以使用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;
}
本文介绍了Linux下服务端与客户端的基本网络编程模型,涉及socket、setsockopt、bind、listen、accept等关键步骤。在编程中,特别注意服务端处理客户端数据返回的逻辑,以及send、write、recv、read函数在不同情况下的返回值。文章还探讨了TCP连接中的MSL等待时间、端口复用以及bind和accept的细节。同时,给出了服务端和客户端的代码示例。
2692

被折叠的 条评论
为什么被折叠?



