声明,本文代码摘自linux内核
服务器端的设计模式:
主要有:套接字初始化,套接字与端口的绑定,设置服务器的侦听链接,接受和发送数据并进行数据处理及处理完毕关闭套接字。
客户端的设计模式:
主要分为套接字初始化,连接服务器,读写网络数据,并进行后续释放资源处理。
这应该是网络编程最基本的框架。刚开始学习,只能从基本的开始。
linux中,每一个应用层函数都会和内核相关的系统调用进行交互:
举个例子:
初始化套接字int sock = socket(AF_INET, SOCK_STREAM, 0); 它会调用系统调用函数sys_socket(AF_INET, SOCK_STREAM, 0);此函数分两个部分,第一生成socket结构,第二与文件描述符绑定,将绑定的描述符传递给应用层。下面贴出源码可以看看:
asmlinkage long sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
retval = sock_map_fd(sock);
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}
sock_create()根据用户的domain指定的协议族,创建一个内核socket结构绑定到当前进程上,其中type和用户空间的设置相同。sock_map_fd()函数将socket结构与文件描述符列表中的某个文件绑定,之后操作可以查找文件描述符列表来对应内核的socket结构。
其他的过程也都是通过系统调用内核函数完成的。
bind()函数对套接字进行地址和端口的绑定,才能进行数据的传送。
应用层与内核函数之间的关系:传递相应的参数给内核。
相应的内核代码:
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err;
if((sock = sockfd_lookup(fd,&err))!=NULL)
{
if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0) {
err = security_socket_bind(sock, (struct sockaddr *)address, addrlen);
if (err) {
sockfd_put(sock);
return err;
}
err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen);
}
sockfd_put(sock);
}
return err;
}
listen()函数,listen 需要维护一个等待队列。故要设置队列的长度。
相应的内核代码:
asmlinkage long sys_listen(int fd, int backlog)
{
struct socket *sock;
int err;
if ((sock = sockfd_lookup(fd, &err)) != NULL) {
if ((unsigned) backlog > sysctl_somaxconn)
backlog = sysctl_somaxconn;
err = security_socket_listen(sock, backlog);
if (err) {
sockfd_put(sock);
return err;
}
err=sock->ops->listen(sock, backlog);
sockfd_put(sock);
}
return err;
}
accept()函数:当服务器成功处理客户端请求连接时,会有两个文件描述符。老的文件描述符表示正在监听的socket,新产生的文件描述符表示客户端的连接。send()和recv()都是通过新的文件描述符来进行数据收发的。
accept内核层代码比较长
asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen)
{
struct socket *sock, *newsock;
int err, len;
char address[MAX_SOCK_ADDR];
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
err = -ENFILE;
if (!(newsock = sock_alloc()))
goto out_put;
newsock->type = sock->type;
newsock->ops = sock->ops;
err = security_socket_accept(sock, newsock);
if (err)
goto out_release;
/*
* We don't need try_module_get here, as the listening socket (sock)
* has the protocol module (sock->ops->owner) held.
*/
__module_get(newsock->ops->owner);
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
if (err < 0)
goto out_release;
if (upeer_sockaddr) {
if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) {
err = -ECONNABORTED;
goto out_release;
}
err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);
if (err < 0)
goto out_release;
}
/* File flags are not inherited via accept() unlike another OSes. */
if ((err = sock_map_fd(newsock)) < 0)
goto out_release;
security_socket_post_accept(sock, newsock);
out_put:
sockfd_put(sock);
out:
return err;
out_release:
sock_release(newsock);
goto out_put;
}
accept内部会新创建一个socket,并进行相应的设置,产生对应的文件描述符。
客户端connect()函数与内核函数之间的关系:
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err;
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
err = move_addr_to_kernel(uservaddr, addrlen, address);
if (err < 0)
goto out_put;
err = security_socket_connect(sock, (struct sockaddr *)address, addrlen);
if (err)
goto out_put;
err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
sock->file->f_flags);
out_put:
sockfd_put(sock);
out:
return err;
}
学习网络基础编程,结合内核代码实现,更好的理解数据传输过程和内核底层都进行了那些操作。
以下为典型的c/s网络编程模型:
/*
* serverDemo.c
*
* Created on: 2016年9月19日
* Author: huwang
*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define PORT 8888
#define BACKLOG 2
void process_conn_server( int s);
int main(int argc, char **argv)
{
int ss, sc;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int err;
pid_t pid;
ss = socket(AF_INET, SOCK_STREAM, 0);
if(ss < 0){
printf("socket error!\n");
return -1;
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
err = bind(ss, (struct sockaddr * )&server_addr, sizeof(server_addr));
if(err < 0){
printf("bind error!\n");
return -1;
}
err = listen(ss, BACKLOG);
if(err < 0){
printf("listen error!\n");
return -1;
}
for( ; ; ){
socklen_t addrlen = sizeof(struct sockaddr);
sc = accept(ss, (struct sockaddr *)&client_addr, &addrlen);
if(sc < 0){
continue;
}
pid = fork();
if(pid == 0){
process_conn_server(sc);
close(ss);
}else{
close(sc);
}
}
}
void process_conn_server( int s)
{
ssize_t size = 0;
char buffer[1024];
for( ; ; ){
size = read(s, buffer, 1024);
if(size == 0){
return;
}
sprintf(buffer, "%zd bytes altogeter\n", size);
write(s, buffer, strlen(buffer) + 1);
}
}
/*
* clientDemo.c
*
* Created on: 2016年9月19日
* Author: huwang
*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#define PORT 8888
int s;
void process_conn_client(int s);
static void sig_handle(int sign);
int main(int argc, char **argv) {
struct sockaddr_in server_addr;
signal(SIGPIPE, sig_handle);
s = socket(AF_INET, SOCK_STREAM, 0);
if(s < 0 ){
printf("socket error!\n");
return -1;
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
process_conn_client(s);
close(s);
return 0;
}
void process_conn_client(int s)
{
ssize_t size = 0;
char buffer[1024];
for(; ;){
size = read(0, buffer, 1024);
if(size > 0){
write(s, buffer, size);
size = read(s, buffer, 1024);
write(1, buffer, size);
}
}
}
static void sig_handle(int sign)
{
if(sign == SIGPIPE)
{
printf("Catch a signpipe signal !\n");
}else if(sign == SIGINT){
printf("Catch a signint signal !\n");
}
close(s);
return;
}
我想在这个基础上可以进行拓展。