TCP、UDP有关函数

我与影子孤独终老i 提交于 2020-03-06 01:27:13

首先看一下上一篇三次握手四次挥手文章中提到的原理图。
其中的read对应的就是recv函数,write对应的就是send函数。
TCP原理图及函数
步入正题,函数的使用:


1.TCP客户端


socket : 创建套接字

函数原型:int socket(int family,int type,int protocol);
功能:创建一个用于网络通信的socket套接字(描述符)
参数:
	@family  :  协议族(AF_INET、AF_INET6、PF_PACKET)
	@type    :  套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)
	@protocol:  协议类别(0、IPPROTO_TCP、IPPROTO_UDP)(一般传0,自动匹配)
返回值:套接字
特点:创建套接字时,系统不会分配端口。
	创建的套接字默认属性是主动的,即主动发起服务的请求;
	当作为服务器时,往往需要修改为被动的。(listen)
头文件:  
	#include <sys/socket.h>

connect : 连接“服务器”

函数原型:
	int connect(int sockfd,const struct sockaddr *addr,socklen_t len);
功能:主动跟服务器建立链接
参数:
	@sockfd   :   socket 套接字
	@addr     :   连接的服务器地址结构
	@len      :   地址结构体长度
返回值:
	成功  :  0   失败  :  其他
注意:
	connect 建立连接之后不会产生新的套接字;
	连接成功后才可以开始传输 TCP 数据。
头文件:
	#include <sys/socket.h>

send : 发送数据到“服务器”

函数原型:
	ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);
功能:用于发送数据
参数:
	@sockfd   :    已建立连接的套接字
	@buf      :    发送数据的地址
	@nbytes   :    发送缓数据的大小(以字节为单位)
	@flags    :    套接字标志(常为0)
返回值:成功发送的字节数
注意:
	不能用发送 0 长度的数据包,这是与sendto的差别。
头文件:
	#include <sys/socket.h>

recv : 接受“服务器”的响应

函数原型:
	ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
功能:用于接收网络数据
参数:
	@sockfd    :    套接字
	@buf       :    接收网络数据的缓冲区的地址
	@nbytes    :    接收缓冲区的大小(以字节为单位)
	@flags     :    套接字标志(常为 0)
返回值:成功接收到字节数
注意:
	一旦接收到0长度的数据包,表示通信结束.
头文件:
	#include <sys/socket.h>

close : 关闭连接

close(socketfd);  //不用了解太多

2.TCP服务器


socket : 创建套接字

函数原型:int socket(int family,int type,int protocol);
功能:创建一个用于网络通信的socket套接字(描述符)
参数:
	@family  :  协议族(AF_INET、AF_INET6、PF_PACKET)
	@type    :  套接字类(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)
	@protocol:  协议类别(0、IPPROTO_TCP、IPPROTO_UDP)(一般传0,自动匹配)
返回值:套接字
特点:创建套接字时,系统不会分配端口。
	创建的套接字默认属性是主动的,即主动发起服务的请求;
	当作为服务器时,往往需要修改为被动的。(listen)
头文件:  
	#include <sys/socket.h>

bind : 确定一个具体的地址

函数原型:
	int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:将本地协议地址与 sockfd 绑定(只能绑定本地主机的IP)
参数:
    @sockfd    :   socket 套接字
    @myaddr    :   指向特定协议的地址结构指针(需要绑定的具体的ip以及port信息)
    @addrlen   :   该地址结构的长度
返回值:
    成功:返回 0          失败:其他

listen : 由主动变被动、创建链接队列、并让操作系统知道这是一个服务 器、而不是客户端。

函数原型:int listen(int sockfd, int backlog);
功能:将套接字由主动修改为被动;
	使操作系统为该套接字设置一个连接队列;
	用来记录所有连接到该套接字的连接。
参数:
	@sockfd    :   socket 监听套接字
	@backlog   :   连接队列的长度
返回值:
	成功:返回0           失败:其他

accept

函数原型:
	int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);
功能:从已连接队列中取出一个已经建立的连接,
	如果没有任何连接可用,则进入睡眠等待(阻塞)
参数:
	@sockfd     :   socket 监听套接字
	@cliaddr    :   用于存放客户端套接字地址结构
	@addrlen    :   套接字地址结构体长度的地址
返回值:
	已连接套接字。(最好while一直探测,套接字接收返回值,建立线程处理这一连接操作)
注意:
	返回的是一个已连接套接字,这个套接字代表当前这个连接。
头文件:
	#include <sys/socket.h>

接收、发送、关闭在上面已经表明。
给大家一个echo并发服务器的例子(原理接收到什么就原路发回去什么----echo)

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
void *deal_client(void *arg);
int main(int argc, char const *argv[])
{
    //sockfd监听套接字(不是用来和客户端通信  只是接受客户端的链接请求)
    int sockfd = socket(AF_INET,SOCK_STREAM,0);

    //服务器必须bind一个固定的ip port8080
    struct sockaddr_in my_addr;
    bzero(&my_addr,sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(atoi(argv[1]));
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));

    //使用listen 由主动变被动 创建链接队列
    listen(sockfd, 10);

    //使用accept提取已完成链接的客户端
    while(1)
    {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        //阻塞
        int new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &len);
        //new_fd 已连接套接字  代表和客户端的真正链接
        //创建一个单独的线程 服务于客户端
        pthread_t tid;
        pthread_create(&tid,NULL,deal_client, &new_fd);
        pthread_detach(tid);

    }

    //关闭监听套接字
    close(sockfd);
    return 0;
}

void *deal_client(void *arg)
{
    int new_fd = *(int *)arg;
     //和客户端通信一下(echo服务器)客户端连接服务器 并发送数据给服务器 服务器收到数据 同时转发给客户端
    char buf[128]="";
    //收
    int ret = recv(new_fd, buf,sizeof(buf),0);
    //原样转发
    send(new_fd,buf,ret,0);

    //关闭已连接套接字
    close(new_fd);
}

**

3.UDP有关函数

**
经过上面的叙述,UDP涉及到的函数只有sendto、recvfrom不知道。
sendto : 发送数据

函数原型:
	ssize_t sendto(int sockfd,const void *buf,\
	size_t nbytes,int flags,const struct sockaddr *to,\
	socklen_t addrlen);
功能:向to结构体指针中指定的 ip,发送 UDP 数据
参数:
	@sockfd     :    套接字
	@buf        :    发送数据缓冲区
	@nbytes     :    发送数据缓冲区的大小
	@flags      :    一般为 0
	@to         :    指向目的主机地址结构体的指针
	@addrlen    :    to 所指向内容的长度
注意:
	通过 to 和 addrlen 确定目的地址;
	可以发送 0 长度的 UDP 数据包。
返回值:
	成功:发送数据的字符数      失败: -1

recvfrom : 接受数据

函数原型:
	ssize_t recvfrom(int sockfd, void *buf,\
	size_t nbytes,int flags,struct sockaddr *from,\
	socklen_t *addrlen);
功能:接收 UDP 数据,并将源地址信息保存在 from 指向的结构中
参数:
	@sockfd    :    套接字
	@buf       :    接收数据缓冲区
	@nbytes    :    接收数据缓冲区的大小
	@flags     :    套接字标志(常为 0)
	@from      :    源地址结构体指针,用来保存数据的来源
	@addrlen   :    from 所指内容的长度
注意:
	通过 from 和 addrlen 参数存放数据来源信息;
	from 和 addrlen 可以为 NULL, 表示不保存数据来源。
返回值:
	成功:接收到的字符数      失败: -1

看完上面的所有函数,可以发现有个参数是未知的,也可以猜测到它包含的成员有很多,那就是struct sockaddr结构体。
这是一个通用套接字地址结构体,用来强转IPv4结构体。

struct sockaddr
{
    sa_family_t sa_family; // 2 字节
    char sa_data[14] //14 字节
};

IPv4 套接字地址结构体

struct sockaddr_in
{
    sa_family_t sin_family;//2 字节
    in_port_t sin_port;//2 字节
    struct in_addr sin_addr;//4 字节
    char sin_zero[8]//8 字节  必须为0
};
struct in_addr
{
    in_addr_t s_addr;//4 字节
};

给IPv4地址结构赋值

//定义服务器IPv4地址结构(假设服务器的IP10.0.121.196  port=8080)
    struct sockaddr_in server_addr; 
    //memset(&server_addr,0,sizeof(server_addr));
    bzero(&server_addr,sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    inet_pton(AF_INET,"10.0.121.196",&server_addr.sin_addr.s_addr)

这其中又出现新的函数:htons、inet_pton,这是一类函数,用来处理大小端带来的问题,就是在多字节、异构计算机、网络通信时有的问题。将在下一篇讲解。
看都看完了,给个赞,点个关注再走吧,带你走进奇妙的计算机世界。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!