首先看一下上一篇三次握手四次挥手文章中提到的原理图。
其中的read对应的就是recv函数,write对应的就是send函数。
步入正题,函数的使用:
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,这是一类函数,用来处理大小端带来的问题,就是在多字节、异构计算机、网络通信时有的问题。将在下一篇讲解。
看都看完了,给个赞,点个关注再走吧,带你走进奇妙的计算机世界。
来源:CSDN
作者:xjpyinxll
链接:https://blog.csdn.net/xjpyinxll/article/details/104682734