TCP/IP 网络编程(二)

本文介绍了TCP/IP网络编程中的已连接和未连接套接字,强调了已连接套接字在多数据传输中的优势。讨论了如何优雅地断开TCP连接,即半关闭状态,并探讨了域名与网络地址的转换。同时,提到了套接字的多种可选项,如缓冲区大小和端口号重用。
摘要由CSDN通过智能技术生成

用户数据包协议(User Datagram Protocal, UDP),UDP服务器端和客户端均只需要 1 个套接字。在TCP中向 10 个客户端提供服务,则除了守门的服务器套接字之外,还需要 10 个服务器端套接字。

UDP不保存连接状态,因此每次传输数据都要添加目标地址信息。

#include <sys/socket.h>

ssize_t sendto(int sock, void *buf, size_t nbytes, int flag, 
struct sockaddr * to, socklen_t addrlen); // 成功时返回传输的字节数,失败时返回-1

~ sock: 用于传输数据的 UDP 套接字文件描述符
~ buf: 待传输数据的缓冲地址值
~ nbytes: 待传输数据的字节长度
~ flag: 可选项参数,没有则传递0
~ to: 目标地址的结构体变量地址
~ addrlen: 地址结构体的长度

接收函数:

#include <sys/socket.h>

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags,
struct sockaddr * from, socklen_t addrlen); // 成功时返回接收的函数,失败时返回-1

~ sock: 用于接受数据的套接字文件描述
~ from: 存有发送端地址信息的结构体变量地址值

基于 UDP 的回声服务器端实例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void err_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock;
    char message[BUF_SIZE];
    int str_len;

    struct sockaddr_in serv_adr;
    struct sockaddr_in clnt_adr;
    socklen_t clnt_adr_sz;


    if(argc != 2)
    {
        printf("Usage : %s <port> \n", argv[0]);
        exit(1);
    }


    serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(serv_sock == -1)
        err_handling("socket() error");

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        err_handling("shenmecuoel bind() error");

    while(1)
    {
        clnt_adr_sz = sizeof(clnt_adr);
        str_len = recvfrom(serv_sock, message, BUF_SIZE, 0,
                            (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        sendto(serv_sock, message, str_len, 0,
                (struct sockaddr*)&clnt_adr, clnt_adr_sz);
    }

    close(serv_sock);
    return 0;
}

void err_handling(char * message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

基于 UDP 的回声客户端实例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char * message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    socklen_t addr_sz;
    struct sockaddr_in serv_addr, from_addr;

    if(argc != 3)
    {
        printf("Usage : %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(sock == -1)
        error_handling("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    while(1)
    {
        fputs("Inputs message(Q to quit): ", stdout);
        fgets(message, sizeof(message), stdin);

        if(!strcmp(message, "Q\n"))
            break;

        sendto(sock, message, strlen(message), 0,
                (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        addr_sz = sizeof(from_addr);
        str_len = recvfrom(sock, message, BUF_SIZE, 0,
                            (struct sockaddr*)&from_addr, &addr_sz);

        message[str_len] = 0;
        printf("message from server : %s \n", message);
    }
    close(sock);
    return 0;
}
void error_handling(char * message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

已连接UDP套接字和未连接UDP套接字

使用sendto分为如下三个阶段:

  1. UDP套接字注册目标IP和端口号
  2. 传输数据
  3. 删除套接字中注册的目标地址信息

这种未注册目标地址信息的套接字成为未连接套接字。默认是未连接的套接字,但是如果向同意目标多次传输数据,这样做显然非常抵消,第一个步骤和第三个步骤消耗了大约三分之一的时间。

要创建已连接的套接字只需要针对UDP套接字调用connect函数。

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = ...;
    serv_addr.sin_port = ...;
    connect(sock,  (struct sockaddr*)&addr, sizeof(addr));

针对UDP套接字调用connect函数并不意味着要与对方UDP套接字连接,这只是向UDP套接字注册目标信息。

注册信息之后再使用 readwrite读写相关的套接字就可以实现信息交互。

优雅的断开连接(TCP的半关闭)

使用shutdown函数实现半关闭

#include <sys/socket.h>

int shutdown(int sock, int howto); // 成功时返回0,失败时返回-1

~ sock: 需要断开的套接字文件描述符
~ howto: 传递断开的方式 SHUT_RD(关闭度连接-流) SHUT_WR(关闭写连接-流) SHUT_REWD(关闭读写连接-流)

域名和网络地址

利用域名获取Ip地址

#include <netdb.h>

struct hostent * gethostbyname(const char * hostname); //成功返回 hostent 结构体指针,失败返回 NULL 指针
struct hostent
{
    char * h_name; // 官方域名,通常不使用
    char ** h_aliases; // 同一主页的多个域名
    int h_addrtype; // 地址族信息,若是IPv4, 保存AF_INET
    int h_length; // IP 地址长度,4字节或是6字节
    char ** h_addr_list; // 同一域名的多个 IP 地址,通过数组保存, h_addr_list 保存数组的地址
}

利用 IP 地址获取域名

#include <netdb.h>

struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family); // 同上

~ addr: 含有IP地址信息的 in_addr 结构体指针
~ len: 第一个参数的地址信息的字节数,46
~ family: 地址族信息

套接字的多种可选项

#include <sys/socket.h>

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen); // 成功返回0, 失败返回-1

~ sock: 要查看的套接字
~ level: 要查看的可选项的协议层 
~ optname: 要查看的可选项名
~ optval: 保存查看结果的缓冲地址值
~ optlen: 缓冲地址大小
#include <sys/socket.h>

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen); // 同上

SO_SNDBUF是输出缓冲区大小,SO_RCVBUF是输入缓冲区大小。

理解Time-Wait状态,调整SO_REUSEADDR参数(置为TRUE),可将Time-Wait状态下的套接字端口号重新分配给新的套接字。

理解Negla算法,通过TCP_NODELAY参数修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值