Socket编程实践_迭代回显服务器_UDP版

Socket编程接口


创建socket套接字的API

函数原型

  • int socket(int af, int type, int protocol)

参数说明

  • af(address famly)为地址族,也就是IP地址类型,常用有AF_INET和AF_INET6。(INET是“internet”的缩写)AF_INET表示ipv4地址,例如:127.0.0.1(127.0.0.1是一个特殊的地址,代表本机地址);AF_INET6表示ipv6地址,例如:1030::C9B4:FF12:48AA:1A2B。
  • type为数据传输方式,常用的有SOCK_STREAM和SOCK_DGRAM。
    前者表示面向有连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或者丢失则重新发送,但效率相对较慢。常见的http协议就是采用的SOCK_STREAM传输数据,因为要保证数据的正确性,否则网页就不能正确解析。

    后者表示面向无连接的数据传输方式。计算机只管传输数据,不做数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。但是这种传输方式的效率比前者高。(一般的视频聊天和语音聊天就使用SOCK_DGRAM传输数据,SOCK_DGRAM也没有想象中那么糟糕,不会频繁的丢失数据,数据错误只是小概率事件。)

  • protocol表示传输协议,常用的有IPPROTO_TCP和IPPTOTO_UDP,分别代表TCP传输协议和UDP传输协议。一般有了af和type两个参数就足够了,因为操作系统会自动推演出协议类型,除非:有两种不同的协议支持同一种地址类型和数据传输类型。如果不是这种特殊情况,那么这个参数就可以填 0。

绑定端口号的API
函数原型

  • int bind(int sock, struct sockaddr *addr, socklen_t addrlen)

参数说明

  • sock为socket文件描述符
  • addr为sockaddr结构体变量的指针
  • addrlen为addr变量的大小,用sizeof(addr)计算得出。

这个函数的第二个参数在传参的时候,我们通常是定义一个sockaddr_in类型的结构体,初始化后,强制类型转换为sockaddr类型的。

sockaddr_in结构体成员说明:

struct sockaddr_in{
    sa_family_t sin_family;
    uin16_t sin_port;
    struct in_addr sin_addr;
    char buf[8];
}

成员说明:
1)sin_family和socket()的第一个参数含义相同,取值也要保持一致。
2)uint16_t 的长度为两个字节,理论上端口号的取值范围为0~65536,但是0~1023的端口一般由系统分配给特定的服务程序(称为知名端口号),例如Web服务的端口号为80,FTP服务的端口号为21,所以我们的程序使用的端口号一般要大于1023,小于65536。(端口号要用htos()函数转换,hots()函数的作用是将本机字节序转换为网络字节序)
3)sin_addr是struct in_addr结构体类型的变量。

struct in_addr{
    in_addr_t s_addr;
}

in_addr_t在头文件< netinet/in.h >中定义,等价于unisgned long,长度为4个字节,也就是说,s_addr是一个整数,而IP地址是一个字符串,所以这个参数需要用inet_addr()函数进行转换,eg:
unsigned long ip = inet_addr("127.0.0.1");
4)sin_zero[8]是多余的8个字节,一般用memset函数将结构体变量全置为0,给前三个成员复制后,第四个成员自然是0。

接收数据并保存源地址的API
函数原型:

int recvfrom(socket s, char *buf, int len, int flags, \
             struct sockaddr *from, int fromlen);

参数说明:

  • s:标识一个已经可用的套接字
  • buf:接收数据的缓冲区
  • len:缓冲区长度
  • flags:调用操作方式(这个参数一般不关心,填0即可)
  • from:指针,指向装有源地址的缓冲区
  • fromlen:指针,指向from缓冲区长度值

向指定目的地发送数据的API
函数原型:

int sendto(socket s, const char *buf, int len, int flags, \
           const struct sockaddr *to, int tolen);

参数说明:

  • s:一个标识套接口的描述字。
  • buf:包含待发送数据的缓冲区。
  • len:buf缓冲区中数据的长度。
  • flags:调用方式标志位。(一般不关心,填0即可)
  • to:(可选)指针,指向目的套接口的地址。
  • tolen:to所指地址的长度。

业务逻辑:
1. 尝试从socket中读取客户端发送的请求
2. 读取到请求后,根据请求内容,计算生成响应
3. 把响应写回到socket中,再传递给客户端

注意:

  1. 客户端不需要调用bind()绑定端口号,因为如果在同一台机器上启动多个客户端,就容易出现端口号已经被占用导致不能成功建立连接的错误。
  2. 服务器端如果不调用bind()绑定端口号,那么内核就会自动给服务器分配监听端口,每次启动服务器时的端口号都不一样,就会导致客户端无法连接服务器。

服务器端代码

#include
#include
#include
#include
#include
#include
#include

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

int main(int argc, char *argv[])
{
    if(argc != 3){
        printf("Usage ./server [ip] [port]\n"_;
        return 1;
    }
    //创建socket文件
    int socket = socket(AF_INET,SOCK_DGRAM, 0);
    if(socket < 0){
        perror("socket");
        return 1;
    }
    //绑定端口号
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    addr.sin_port = htons(atoi(argv[2]));
    int ret = bind(sock, (sockaddr *)&addr, sizeof(addr));
    if(ret < 0){
        perror("bind");
        return 1;
    }
    while(1){
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        char buf[1024] = {0};
        //从socket中接收消息
        ssize read_size = recvfrom(sock, buf, sizeof(buf) - 1,\
                            0, (sockaddr*)*peer,&len);
        if(read_size < 0){
            perror("recvfrom");
            continue;
        }
        buf[read_size] = '\0';
        //将消息打印到屏幕上
        printf("[%s :%d] %s\n",inet_ntoa(peer.sin_addr), \
                ntohs(peer,sin_port), buf);
        //将消息发送回客户端
        sendto(sock, buf, strlen(buf), 0, \
                (sockaddr*)&peer, sizeof(peer));
    }
    close(sock);
    return 0;
}

客户端代码

#include
#include
#include
#include
#include
#include
#iclude

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;


//通过命令行参数,指定客户端向哪个服务器发送服务
int main(int argc, char *argv[])
{
    if(argc != 3){
        printf("Usage ./client [ip] [port]\n");
        return 1;
    }
    int sock = socket(AF_INET, SOCK_DGRAM,0);
    if(sock < 0){
        perror("sock");
        return 1;
    }
    sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argc[1]);
    server_addr,sin_port = htons(atoi(argv[2]));

    while(1){
        //1.从标准输入读取数据
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if(read_size < 0){
            perror("read");
            close(socket);
            return 1;
        }
        if(read_size == 0){
            printf("read done!\n");
            close(socket);
            return 0;
        }
        buf[read_size] = '\0';
        //2.把数据发送给服务器
        sendto(socket, buf, strlen(buf), 0, \
              (sockaddr*)&server_addr, sizeof(server_addr));
        //3.尝试从服务器读取数据
        //此时revcfrom不需要知道对端的IP和端口号
        //因为收到的数据一定是服务器发回来的
        //而服务器对应的IP端口号已知
        char buf_output[1024] = {0};
        read_size = recvfrom(sock, buf_output, \
                        sizeof(buf_output) - 1, 0, NULL, NULL);

        //4.把读取到的结果写到标准输出上
        printf("server resp %s\n",buf_output);
    }

    close(socket);
    return 0;
}

你可能感兴趣的:(Network)