客户/服务器模型
在我们日常所见的网络应用程序中,很多都是由客户/服务器(C/S)模型组成的。服务器主要承担着提供资源的责任,通常可以为数量较多的客户提供服务。今天我们主要来通过一个最基本的回射服务器和客户端模型的编写,来体会到客户和服务器的角色定位,以及对套接字有更深的理解。首先要有一些准备知识,了解在Linux下的网络编程的套接字接口。socket函数功能:创建一个套接字用于进程通信。套接字...
在我们日常所见的网络应用程序中,很多都是由客户/服务器(C/S)模型组成的。
服务器主要承担着提供资源的责任,通常可以为数量较多的客户提供服务。
今天我们主要来通过一个最基本的回射服务器和客户端模型的编写,来体会到客户和服务器的角色定位,以及对套接字有更深的理解。
首先要有一些准备知识,了解在Linux下的网络编程的套接字接口。
socket函数
功能:创建一个套接字用于进程通信。套接字在本质上就是一个文件描述符。
int socket(int domain, int type, int protocol);
/*
domain:指定通信协议族 //IPV4通常为AF_INET
type:指定socket类型,流式套接字SOCK_STREAM(TCP),数据报套接字SOCK_DGRAM(UDP),原始套接字SOCK_RAW
protocol:协议类型(如控制传输层、网络层或者是链路层)
返回值:成功返回非负整数,与文件描述符类似,称为套接口描述字,简称套接字。失败返回-1。
*/
bind函数
功能:绑定一个本地地址到套接字上。
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
/*
sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
返回值:成功返回0,失败返回-1
*/
listen函数
功能:将套接字用于监听进入的连接
int listen(int sockfd, int backlog);
/*
sockfd:socket函数返回的套接字
backlog:规定内核为此套接字排队的最大连接个数
返回值:成功返回0,失败返回-1
*/
使用listen函数之后,套接字就会变成被动套接字,是用来等待连接的,一般用于服务器。需要继续调用accept函数。
对于给定的监听套接字接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三次握手的过程
2、已经完成连接的队列
最大连接个数等于已完成连接队列加上未完成连接的队列。
accept函数
功能:从已经完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);
/*
sockfd:socket函数返回的服务器套接字
addr:将返回对等方的套接字地址
addrlen:返回对等方的套接字地址长度
返回值:成功返回0,失败返回-1
*/
connect函数
功能:建立一个连接至addr所指定的套接字
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
/*
sockfd:socket函数返回的未连接的套接字
addr:要连接的套接字地址
addrlen:第二个参数addr的长度
*/
了解完这些基本接口后,还需要知道的是一些套接字的地址结构,如果不了解可以点这里。
在使用套接字地址的时候记得初始化。
下面是一段常见的套接字初始化的方式。
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2])); //argv[2]代表由命令行参数传入的端口号
server.sin_addr.s_addr = inet_addr(argv[1]); //argv[1]代表由命令行参数传入的IP地址
有了这些知识的准备后,我们很容易就可以编写一个基础的TCP服务器。代码示例如下:
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main(int argc, char* argv[])
{
if(argc != 3){
printf("Usage: %s, [IP], [port]\n", argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 2;
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sock, (struct sockaddr*)&server, sizeof(server)) < 0){
perror("bind");
return 3;
}
if(listen(sock, 10) < 0){
perror("listen");
return 4;
}
struct sockaddr_in client;
socklen_t len = sizeof(client);
int client_fd;
if((client_fd = accept(sock, (struct sockaddr*)&client, &len)) < 0){
perror("accept");
return 5;
}
char buf[1024] = {0};
while(1){
memset(buf, 0, sizeof(buf));
int ret = read(client_fd, buf, sizeof(buf)-1);
buf[strlen(buf)] = '\0';
printf("%s", buf);
write(client_fd, buf, ret);
}
close(sock);
return 0;
}
下面是客户端的代码示例:
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main(int argc, char* argv[])
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0){
perror("socket");
return 2;
}
struct sockaddr_in client;
memset(&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(atoi(argv[2]));
client.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock, (struct sockaddr*)&client, sizeof(client)) < 0){
perror("connect");
return 3;
}
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};
while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){
write(sock, sendbuf, strlen(sendbuf));
read(sock, recvbuf, sizeof(recvbuf));
printf("%s", recvbuf);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sock);
return 0;
}
这一份示例代码还有很多不完善的地方,如只能接收一个客户的请求,在服务器主动断开连接后,会进入TIME_WAIT状态等缺点,如果有兴趣,可以了解setsockopt函数。
更多推荐
所有评论(0)