Linux网络编程(2)——采用TCP的基本server的实现

一个基本的C/S服务器模型很简单:         客户端    <------------------------>    服务器

简而言之就是客户端跟服务器之间的通话,通话方式一般采用TCP和UDP这两种。

TCP和UDP区别

1、Tcp提供客户与服务器之间的连接。TCP客户端先与某个给定服务器建立一个连接,再跨该连接于那个服务器交换数据,然后终止这个连接。

(连接其实就是一种协商机制,预先定义好了双方的一些状态变量,告诉对方诸如序列号和通告窗口大小等状态信息)

2、Tcp提供了可靠的传输机制,不需要像UDP那样需要通过应用层来实现可靠性,直接通过协议来实现。发送数据后等待对方确认,没有收到确认就继续重传与等待,数次重传失败后才会放弃。

3、Tcp提供了流量控制,tcp总是告诉对端在任何时刻它一次能够从对端接收多少字节的数据。


采用套接字的TCP连接的基本程序模型

就是通过socket的这个函数封装数据,实现server额client的通信

客户端                                          服务端

sokcket                                       socket()          //创建套接字描述符

                                                     bind()             //将服务器地址和相应套接字描述符绑定

                                                     listen()          //将主动套接字转换为监听套接字,该套接字可以接受来自客户端的请求

connect()   <------------->       accept()      //connect()客户端连接请求 , accpet()函数解析监听来的客户端信息,

write()       ------------>            read()        //网络读写io

read()       <--------------         write()

close()             ------------>          read()

                                                     close()           //close关闭连接


函数接口

1、socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
若成功返回非负描述符,若出错返回-1

domain网络类型(一般默认为AF_INET因特网),type套接字类型(一般默认为SOCK_STREAM),protocol协议号

2、connect

#include<sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
若成功返回0,若出错返回-1

connect函数试图与套接字地址为serv_addr的服务器建立一个因特网连接,addrlen默认为sizeof(sockaddr_in)

3、bind

#include<sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
若成功返回0,若出错返回-1
将服务器地址和套接字联系起来

4、listen

#include<sys/socket.h>
int listen(int sockfd, int backlog);
若成功返回0,若出错返回-1 

 告诉内核,这个是服务器创建的套接字,不是客户端的,将主动状态转换为被动状态。 

5、accept

#include<sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
若成功返回0,若出错返回-1 

 
 等待来自于客户端的连接请求到达监听描述符listenfd,然后在addr中填写客户端的套接字地址,并返回一个已连接描述符,这个描述符可以用来利用Unix I/O函数与客户端通信。 

套接字地址结构体

<netinet/in.h>
struct in_addr{
    in_addr_t s_addr;            //32bit ipv4 address
}

struct sockaddr_in{
    uint8_t;                          
    sa_family_t sin_family;           // AF_INET
    in_port_t sin_port;               //端口
    struct in_addr sin_addr;
    char sin_zero[8];                //未使用
};

DNS主机条目结构体

通过调用gethostbyname和gethostbyaddr函数,从DNS数据库中检索任意的主机条目。

<pre name="code" class="cpp">#include <netdb.h>
struct hostent{ char *h_name; // 主机域名 char **h_aliases; int h_addrtype; //主机地址 int h_length; char **h_addr_list;
};
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const char* addr, int len, int domain);

 

具体代码实现:

实现一个简单的客户端服务器模型,并不涉及多线程、I/O复用此类

server.c  服务端

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

#define MAX_LISTEN 1024
#define MAX_LINE 1024

int Socket(int domain, int type, int protocol){
    int sockfd = socket(domain, type, protocol);
    if ( sockfd < 0 ){
        perror("init socket:  ");
        exit(0);
    }
    return sockfd;
}

void Bind(int sockfd, struct sockaddr *myaddr, int addrlen){
    if ( bind(sockfd, myaddr, addrlen) < 0 ){
        perror("bind");
        exit(0);
    } }

void Listen(int sockfd, int backlog){
    if ( listen(sockfd, backlog) < 0){
        perror("listen");
        exit(0);
    }
}

int Accept(int listenfd, struct sockaddr *addr, int *addrlen){
    int clientfd = accept(listenfd, addr, addrlen);
    if ( clientfd < 0){
        perror("accept");
        exit(0);
    }
    return clientfd;
}

void Close(int clientfd){
    if ( close(clientfd) < 0){
        perror("close");
        exit(0);
    }
}
struct hostent* Gethostbyaddr(const char *addr, int len, int domain){
    struct hostent* host = gethostbyaddr(addr, len, domain);
    if ( NULL == host ){
        perror("host_by_addr");
        exit(0);
    }
    return host;
}

ssize_t Read(int fd, void* buf, size_t n){
    ssize_t num= read(fd, buf, n);
    if ( n < 0){
        perror("read");
        exit(0);
    }
    return num;
}

ssize_t Write(int fd, const void* buf, size_t n){
    ssize_t num = read(fd, buf, n);
    if ( n < 0){
        perror("write");
        exit(0);
    }
    return num;
}

void echo(listenfd){
    ssize_t n;
    char write_buff[MAX_LINE];
    char read_buff[MAX_LINE];
    
    memset(write_buff, 0, MAX_LINE);
    memset(read_buff, 0, MAX_LINE);

    n = read(listenfd, read_buff, MAX_LINE);
    read_buff[n] = '\0';

    strcpy(write_buff, "from server echo: ");
    strcpy(write_buff+strlen("from server echo: "), read_buff);

    n = write(listenfd, write_buff, MAX_LINE);

    
}

int main(int argc, char **argv){
    int servfd, clientfd, port, clientlen;
    struct sockaddr_in servaddr;
    struct sockaddr_in cliaddr;
    struct hostent *host;
    char* hostaddr;
    if ( argc != 2){
        fprintf(stderr,"usage:%s<port>\n", argv[0]);
        exit(0);
    }
    port = atoi(argv[1]);  // get port

    servfd = Socket(AF_INET, SOCK_STREAM, 0);
    
    // init servaddr
    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons((unsigned short)port);
    clientlen = sizeof(cliaddr);
    
    Bind(servfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    Listen(servfd, MAX_LISTEN);

    while(1){   // init server
        memset(&cliaddr, 0, sizeof(cliaddr));
        clientfd = Accept(servfd, (struct sockaddr*)&cliaddr, &clientlen);
        host = Gethostbyaddr((const char*)&cliaddr.sin_addr.s_addr, sizeof(cliaddr.sin_addr.s_addr), AF_INET);
        printf("server connect to host: %s %s\n",host->h_name, inet_ntoa(cliaddr.sin_addr));
        echo(clientfd);
        Close(clientfd);
    }
}

client.c  客户端

#include<stdio.h> 
#include<sys/socket.h> 
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<netdb.h>

#define MAX_LINE 1024

int Socket(int domain, int type, int protocol){
    int sockfd = socket(domain, type, protocol);
    if ( sockfd < 0 ){
        perror("init socket");
        exit(0);
    }
    return sockfd;
}
void Close(int clientfd){
    if ( close(clientfd) < 0){
        perror("close");
        exit(0);
    }
}
struct hostent* Gethostbyaddr(const char *addr, int len, int domain){
    struct hostent* host = gethostbyaddr(addr, len, domain);
    if ( NULL == host ){
        perror("host_by_addr");
        exit(0);
    }
    return host;
}

ssize_t Read(int fd, void* buf, size_t n){
    if ( read(fd, buf, n) < 0){
        perror("read");
        exit(0);
    }
}

ssize_t Write(int fd, const void* buf, size_t n){
    if ( write(fd, buf, n) < 0){
        perror("write");
        exit(0);
    }
}

void Connect(int sockfd, struct sockaddr* serv_addr, int addrlen){
    if ( connect(sockfd, serv_addr, addrlen) < 0){
        perror("connect");
        exit(0);
    }
}

void message_handle(int clientfd){
    size_t n;
    char send_buff[MAX_LINE];
    char recv_buff[MAX_LINE];
    memset(send_buff, 0, MAX_LINE);
    memset(recv_buff, 0, MAX_LINE);

    fgets(send_buff, MAX_LINE, stdin);
    send_buff[strlen(send_buff)-1] = '\0';

    n = Write(clientfd, send_buff, strlen(send_buff)+1);
    n = Read(clientfd, recv_buff, MAX_LINE);

    printf("%s \n", recv_buff);
}

int main(int argc, char **argv){
    int  clientfd, port;
    struct sockaddr_in servaddr;
    if ( argc != 3){
        fprintf(stderr,"usage:%s<addr> <port>\n", argv[0]);
        exit(0);
    }
    port = atoi(argv[2]);
    printf("port:  %d\n", port);
    printf("addr: %s\n", argv[1]);

    clientfd = Socket(AF_INET, SOCK_STREAM, 0);

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    Connect(clientfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); 
    message_handle(clientfd);
    Close(clientfd);
}



代码分析:

服务端按上图TCP模型建立服务器处理客户端连接,每当有客户端连接时即打印客户信息,然后接受到客户发来的消息后,将客户消息反射回客户端。

运行结果:

客户端:



服务端:

客户端中断后使用netstat -a 命令查看tcp连接状态,9000端口处于listen状态,该连接处于TIME_WAIT状态



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值