服务器/客户端简单实现(初学网络编程)

声明,本文代码摘自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;
}

我想在这个基础上可以进行拓展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值