网络编程入门_回显服务器

以回显服务器为例,整理一下网络编程的基础知识。

code

  • 服务端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #include <iostream>
    #include "unp.h"
    using namespace std;

    int main(){
    int listenfd,connfd;
    pid_t childpid;
    socklen_t clilen;
    struct sockaddr_in cliaddr,servaddr;
    listenfd=socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(SERV_PORT);
    bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
    listen(listenfd,LISTENQ);
    int i=0;
    for(;;){
    clilen=sizeof(cliaddr);
    connfd=accept(listenfd,(SA*)&cliaddr,&clilen);
    if((childpid=fork())==0){
    close(listenfd);
    str_echo(connfd);
    exit(0);
    }
    close(connfd);
    i++;
    cout<<i<<" "<<endl;
    }


    return 0;
    }

    void str_echo(int sockfd){
    ssize_t n;
    char buf[MAXLINE];
    again:
    while((n=read(sockfd,buf,MAXLINE))>0){
    write(sockfd,buf,n);
    }
    if(n<0 && errno==EINTR){
    goto again;
    }else if(n<0){
    cout<<"str_echo: read error"<<endl;
    }
    }
  • 客户端程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <iostream>
    #include "unp.h"
    using namespace std;

    int main(int argc,char **argv){
    int sockfd;
    struct sockaddr_in servaddr;
    if(argc!=2){
    cout<<"usage:echoclie <ip addr>"<<endl;
    return 0;
    }
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0){
    cout<<"socket create error"<<endl;
    return 0;
    }
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(SERV_PORT);
    inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
    int conn=connect(sockfd,(SA*)&servaddr,sizeof(servaddr));
    if(conn<0){
    cout<<"connect create error"<<endl;
    return 0;
    }
    str_cli(stdin,sockfd);
    return 0;
    }

    void str_cli(FILE *fp,int sockfd){
    char sendline[MAXLINE],recvline[MAXLINE];
    while(fgets(sendline,MAXLINE,fp) != NULL){
    write(sockfd,sendline,strlen(sendline));
    //if(readline(sockfd,recvline,MAXLINE)==0){
    if(read(sockfd,recvline,MAXLINE)<0){
    cout<<"str_cli:server terminated prematurely"<<endl;
    }
    fputs(recvline,stdout);
    }
    }

TCP连接过程

TCP连接过程

服务端用到的几个网络编程api

建立socket套接字

  • 函数原型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int socket(int domain, int type, int protocol);
    domain 具体通信的域
    AF_UNIX Local communication
    AF_LOCAL Synonym for AF_UNIX
    AF_INET(常用) IPv4 Internet protocols
    type 通信类型
    SOCK_STREAM(常用) Provides sequenced, reliable, two-way, connection-
    based byte streams. An out-of-band data transmission
    mechanism may be supported.
    SOCK_DGRAM Supports datagrams (connectionless, unreliable
    messages of a fixed maximum length).
    protocol 用来设置用tcp还是udp,一般为0

sockaddr_in结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
include <netinet/in.h>

struct sockaddr {
unsigned short sa_family; // 2 bytes address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};

// IPv4 AF_INET sockets:

struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 bytes e.g. htons(3490)
struct in_addr sin_addr; // 4 bytes see struct in_addr, below
char sin_zero[8]; // 8 bytes zero this if you want to
};

struct in_addr {
unsigned long s_addr; // 4 bytes load with inet_pton()
};

sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:
程序员不应操作sockaddr,sockaddr是给操作系统用的
程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便。
一般的用法为:
程序员把类型、ip地址、端口填充sockaddr_in结构体,然后强制转换成sockaddr,作为参数传递给系统调用函数

bzero置零函数

memset()函数也可完成这个功能,但是在填写参数时容易出错并且编译器不会提示,所以改用bzero

1
2
3
4
include <strings.h>
void bzero(void *s, size_t n);
%%%%
bzero(&servaddr,sizeof(servaddr));

字节序转换hton函数

servaddr.sin_addr.s_addr=htonl(INADDR_ANY);

1
2
3
4
5
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
用来将主机字节序的无符号整型转换为网络字节序

bind将ip和端口绑定到socket

1
2
3
4
5
6
bind(listenfd,(SA*)&servaddr,sizeof(servaddr));
函数原型:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd socket套接字的描述符
addr 储存ip和端口
addrlen 前一个参数结构体的所占字节长度

监听套接字listen

listen(listenfd,LISTENQ);

1
2
3
4
5
include <sys/socket.h>
int listen(int socket,int backlog)
socket 监听的套接字描述符
backlog
系统维护两个队列,分别是已完成三路握手的队列和未完成的,两个队列的长度不会超过backlog

接受连接accept

1
2
3
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
该函数会提取未完成队列中的第一个连接请求,完成三路握手,然后返回一个文件描述符,后续可以用来读写。
如果未完成队列为空,则默认会阻塞。

客户端用到的api

发起连接请求connect

1
2
3
int conn=connect(sockfd,(SA*)&servaddr,sizeof(servaddr));
客户端有connect()来发起请求。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

参考

欢迎与我分享你的看法。
转载请注明出处:http://taowusheng.cn/