网络编程之TCP1

1. 知识点

本地地址的赋值通常需要由你的程序手动指定,尤其是在服务器端的情况下。在服务器程序中,你通常需要指定服务器应该监听的本地地址和端口号。这是通过构建一个适当的套接字地址结构体并将其传递给 bind 函数来实现的。

在客户端程序中,通常不需要手动指定本地地址,因为客户端通常会让操作系统自动选择本地地址。(即bind函数可以省略)在这种情况下,你可以将套接字地址结构体的相关字段设置为0或空,然后让操作系统为你选择适当的本地地址。

1.1 tcp通信服务端和客户端流程

1.1.1 TCP服务端流程:

1创建套接字(socket() ) 服务端首先创建一个套接字(Socket)来监听客户端的连接请求。通常使用socket()函数创建套接字。

2. 绑定套接字(设置struct sockaddr_in结构体  和  bind() ) 服务端将套接字绑定到一个特定的IP地址和端口号上,以侦听客户端的连接请求。使用bind()函数来执行此操作。

3开始监听(listen() ) 服务端使用listen()函数开始监听来自客户端的连接请求。可以指定允许等待连接的队列大小。

4. 接受连接请求(accept() ): 当有客户端尝试连接到服务器时,服务端使用accept()函数来接受连接请求。accept()函数将创建一个新的套接字,用于与该客户端进行通信。

5. 与客户端通信( recv()   或者  send() ) 一旦连接建立,服务端和客户端之间可以进行数据交换。服务端可以使用新的套接字来接收来自客户端的数据,并发送响应。

6关闭连接(close() ) 当通信完成后,服务端可以使用close()函数关闭与客户端的连接。通常,在循环中等待更多客户端连接请求或关闭服务。

1.1.2 TCP客户端流程:

1创建套接字(socket() ) 客户端首先创建一个套接字,以连接到服务器。使用socket()函数来创建套接字。

2指定服务器信息设置struct sockaddr_in结构体 ) 客户端需要指定服务器的IP地址和端口号,以便建立连接。通常,这些信息存储在struct sockaddr_in中。

3连接到服务器(connect() ) 使用connect()函数,客户端尝试与服务器建立连接。如果连接成功,客户端将能够与服务器通信。

4与服务器通信(recv()   或者  send()): 一旦连接建立,客户端可以使用套接字来发送数据给服务器,并接收服务器的响应数据。

5. 关闭连接(close() ) 当通信完成后,客户端使用close()函数来关闭与服务器的连接。

网络编程之TCP1_第1张图片

网络编程之TCP1_第2张图片

2. TCP 通信

2.1 相关函数

2.1.1 创建套接字

接口声明:int socket(int domain, int type, int protocol);

参数:

domain:域。

AF_INET/PF_INET: 网际协议

AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCAL

type:类型。

SOCK_STREAM:流式套接字     TCP

SOCK_DGRAM:数据包套接字    UDP

protocol:协议。

一般为 0

返回值:

成功:待连接套接字

失败:-1

备注:在网际协议中,选择流式套接字就代表TCP协议,选择数据包套接字就代表UDP协议

第三个参数 protocol 一般都不用。

2.1.2 绑定套接字与网络地址

接口声明:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

sockfd:待连接套接字

addr:包含本地地址(IP+PORT)的通用地址结构体的指针

addrlen:地址结构体大小

返回值:

成功:0

失败:-1

备注:

通用地址结构体的定义:

struct sockaddr

{

sa_family_t sa_family;

char sa_data[14];

};

特殊地址结构体 —— IPv4 地址结构体:

struct sockaddr_in

{

u_short   sin_family; // 地址族,与socketI() 函数的第一个参数相同,填AF_INET

u_short   sin_port; // AF_INET端口,0-----1023, 1024-----5000,5000------65535

struct in_addr  sin_addr; // IPV4 地址  32位地址

char sin_zero[8];

};

struct in_addr

{

in_addr_t s_addr; // 无符号 32 位网络地址sockaddr. sin_addr. s_addr = htons(192.168.124.195)

};

特殊地址结构体 —— IPv6 地址结构体:

struct sockaddr_in6

{

u_short sin6_family; // 地址族

__be16 sin6_port; // 端口

__be32 sin6_flowinfo; // 流信息

struct in6_addr sin6_addr; // IPv6 地址

__u32 sin6_scope_id;

};

特殊地址结构体 ——UNIX 域地址结构体:

struct sockaddr_un

{

u_short sun_family; // 地址族

char sun_path[108]; // 套接字文件路径

};

2.1.3  将待连接套接字设置为监听套接字,并设置最大同时接收连接请求个数

接口声明:int listen(int sockfd, int backlog);

参数:

sockfd:待连接套接字

backlog:最大同时接收连接请求个数

返回值:

成功:0,并将 sockfd 设置为监听套接字

失败:-1

备注:

由于历史原因,各种系统对 backlog 的理解并不一致,以 LINUX 为例,监听端能同时接收的

最大连接请求个数为 0+4

2.1.4 等待对端连接请求

接口声明:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

sockfd:监听套接字

addr:通用地址结构体,用以存储对端地址(IP+PORT)(也就是存对方的地址,服务端不设置就写NULL)

addrlen:参数 addr 的存储区域大小

返回值:

成功:已连接套接字(非负整数)

失败:-1

2.1.5 请求连接对端监听套接字

接口声明:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

sockfd:待连接套接字

addr:包含对端地址(IP+PORT)的通用地址结构体的指针

addrlen:地址结构体大小

返回值:

成功:0

失败:-1

2.1.6 断开本端连接套接字

接口声明:int close(int fd);

参数:

fd:已连接套接字

返回值:

成功:0

失败:-1

备注:

同时断开读端和写端

2.1.7  断开本端连接套接字

接口声明:int shutdown(int sockfd, int how);

参数:

sockfd:已连接套接字

how:断开方式。

SHUT_RD:关闭读端

SHUT_WR:关闭写端

SHUT_RDWR:同时关闭读写端

返回值:

成功:0

失败:-1

备注:

在只关闭一端的时候,另一端可以继续使用。

2.1.8 将文本地址转化为二进制地址

Char *src=”192.168.124.115”

接口声明:int inet_pton(int af, const char *src, void *dst);

参数:

af:地址族。

AF_INET:IPv4 地址

AF_INET6:IPv6 地址

src:指向“点分式”IPv4 或 IPv6 地址的指针,例如“192.168.1.100”

dst:类型为 struct in_addr *或者 struct in6_addr *的指针

返回值:

成功:1

失败:0 代表地址与地址族不匹配,-1 代表地址不合法

2.1.9 将二进制地址转化为文本地址

接口声明:const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

af:地址族。

AF_INET:IPv4 地址

AF_INET6:IPv6 地址

src:类型为 struct in_addr *或者 struct in6_addr *的指针

dst:地址缓冲区指针,缓冲区至少

size:地址缓冲区大小至少要 INET_ADDRSTRLEN 或者 INET6_ADDRSTRLEN 个字节

返回值:

成功:dst

失败:NULL

2.1.10 向 TCP 套接字发送数据

接口声明:ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

sockfd:已连接套接字

buf:即将被发送的数据

len:数据长度

flags:发送标志。

MSG_NOSIGNAL:当对端已关闭时,不产生 SIGPIPE 信号

MSG_OOB:发送紧急(带外)数据,只针对 TCP 连接

返回值:

成功:已发送字节数

失败:-1

备注:

当 flags 为 0 时,send 与 write 作用相同。

2.1.11向 UDP 套接字发送数据

接口声明:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr*dest_addr, socklen_t addrlen);

参数:

sockfd:UDP 套接字

buf:即将发送的数据

len:数据的长度

flags:发送标志,与函数 send 的 flags 完全一致

dest_addr:对端网络地址

addr_len:地址长度

返回值:

成功:已发送字节数

失败:-1

备注:

当 dest_addr 为 NULL,addrlen 为 0 时,sendto 与 send 作用一致

2.1.12从 TCP 套接字接收数据

接口声明: ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

sockfd:已连接套接字

buf:存储数据缓冲区

len:缓冲区大小

flags:接收标志

MSG_OOB:接收紧急(带外)数据

返回值:

成功:已接收字节数

失败:-1

备注:

当 flags 为 0 时,recv 与 read 作用相同。阻塞等待

2.1.13从 UDP 套接字接收数据

接口声明:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

参数:

sockfd:UDP 套接字

buf:储存数据缓冲区

len:缓冲区大小

flags:接收标志,与函数 send 的 flags 完全一致

src_addr:对端网络地址

addrlen:地址长度

返回值:

成功:已接收字节数

失败:-1

2.1.14多路复用

接口声明:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

参数:

nfds:所有正在监测的套接字的最大值加 1

readfds:读就绪文件描述符集合

writefds:写就绪文件描述符集合

exceptfds:异常就绪文件描述符集合

timeout:超时控制

返回值:

成功:就绪文件描述符总数(当超时返回时为 0)

失败:-1

备注:

2.1.15 文件描述符集合操作函数:

void FD_CLR(int fd, fd_set *set);

int FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

2.1.16多路复用

接口声明:int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数:

fds:监测文件描述符结构体数组

nfds:数组元素个数

timeout:超时控制

返回值:

成功:

失败:

备注:

struct pollfd

{

int fd; // 监测的文件描述符

short events; // 监测的状态

short revents; // 实际发生的状态

};

其中,监测的状态可以用以下宏来标记:

POLLIN:读就绪

POLLPRI:紧急数据读就绪

POLLOUT:写就绪

POLLRDHUP:对端已关闭或已关闭写端(仅对流式套接字有效)

POLLERR:发生错误

2.1.17字节序转换

接口声明:

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

参数:

hostlong: 主机字节序的长整型数据

hostshort: 主机字节序的短整型数据

netlong: 网络字节序的长整型数据

netshort: 网络字节序的短整型数据

返回值:

对应的字节序数据

3. 举例

3.1 服务器编写

server.c

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


//服务端和客户端通信(但是服务单只负责接收,客户端负责发送)
//服务端程序

int main(int argc,char** argv)
{
    //1.创建待连接套接字
    int socket_fd = socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd == -1){
        printf("创建套接字失败\n");
        return -1;
    }

    //2.绑定套接字与网络地址
    //第二个参数需要这个结构体
    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));//设置端口 //主机字节序的短整型数据
    s_addr.sin_addr.s_addr = inet_addr(argv[1]);//将将点分十进制转转为无符号的32位网络地址

    int bind_ok = bind(socket_fd,(struct sockaddr*)&s_addr,sizeof(s_addr));
    if(bind_ok==-1){
        perror("bind failed");
    }

    //3.设置监听
    int socket_listen = listen(socket_fd,4);
    if(socket_listen==-1){
        perror("监听失败");
        return -1;
    }

    //4.等待对端连接请求
    printf("等待连接\n");
    int socket_ok = accept(socket_fd,NULL,NULL);
    if(socket_ok==-1){
        perror("连接失败\n");
        return -1;
    }
    printf("连接成功\n");

    //开始畅聊吧
    char buf[128];
    while(1){
        memset(buf,0,sizeof(buf));
        //5.接收消息
        recv(socket_ok,buf,sizeof(buf),0);//阻塞等待
        printf("接收到:%s",buf);

        //退出
        if(strcmp("bye\n",buf)==0){
            break;
        }
    }
    close(socket_fd);
    close(socket_ok);
    return 0;
}

3.2 客户端编写

client.c

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


//服务端和客户端通信(但是服务单只负责接收,客户端负责发送)
//客户端程序

int main(int argc,char** argv)
{
    //1.创建待连接套接字
    int socket_fd = socket(AF_INET,SOCK_STREAM,0);
    if(socket_fd == -1){
        printf("创建套接字失败\n");
        return -1;
    }

    //2.绑定套接字与网络地址
    //第二个参数需要这个结构体
    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(atoi(argv[2]));//设置端口//主机字节序的短整型数据
    s_addr.sin_addr.s_addr = inet_addr(argv[1]);//将点分十进制转为无符号的32位网络地址

    int bind_ok = bind(socket_fd,(struct sockaddr*)&s_addr,sizeof(s_addr));
    if(bind_ok==-1){
        perror("bind failed");
    }


    //3.请求连接
    int socket_ok = connect(socket_fd,(struct sockaddr*)&s_addr,sizeof(s_addr));
    if(socket_ok==-1){
        perror("连接失败\n");
        return -1;
    }

    //开始畅聊吧
    char buf[128];
    while(1){

        memset(buf,0,sizeof(buf));

        fgets(buf,sizeof(buf),stdin);//从键盘获取输入
        //发送消息
        send(socket_fd,buf,sizeof(buf),0);
        printf("发送:%s",buf);

        //退出
        if(strcmp("bye\n",buf)==0){
            break;
        }
    }

    close(socket_fd);
    return 0;
}

你可能感兴趣的:(网络编程,网络,tcp/ip,服务器,c语言,算法,信号处理)