一、UDP全双工通信
UDP通信基础:
recvfrom函数
recvfrom
是一个用于接收数据的函数,,但 recvfrom
不仅接收数据,还可以获取发送数据的地址信息。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
-
sockfd
: 指定用于接收数据的套接字描述符。它通常是通过socket()
函数创建的。 -
buf
: 指向接收数据的缓冲区的指针。 -
len
: 指定缓冲区的大小,即要接收的数据的最大字节数。 -
flags
: 接收数据时的标志位,默认为0 -
src_addr
: 指向存储发送方地址信息的sockaddr
结构体的指针。如果不关心发送方的地址信息,可以传入NULL
。 -
addrlen
: 指向src_addr
结构体长度的指针。在函数返回时,它将包含实际的地址长度。
- 成功时,
recvfrom
返回接收到的字节数。 - 出错时,返回
-1
bind函数
bind
函数在网络编程中用于将一个套接字与一个特定的地址(IP地址和端口号)绑定。它主要用于服务器端,使得服务器能够监听来自特定地址的连接请求。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
sockfd
: 套接字文件描述符。这个套接字是通过socket()
函数创建的,表示一个未绑定的套接字。 -
addr
: 指向sockaddr
结构体的指针,包含要绑定到的地址信息。通常使用sockaddr_in
结构体来表示IPv4地址(对于IPv6使用sockaddr_in6
),然后将其强制转换为sockaddr*
类型。 -
addrlen
: 结构体sockaddr
的大小,通常使用sizeof(struct sockaddr_in)
或sizeof(struct sockaddr_in6)
。 - 成功时,返回
0
。 - 出错时,返回
-1
struct sockaddr结构体:
struct sockaddr 通用地址结构 --- ip + 端口
{
u_short sa_family; 地址族
char sa_data[14]; 地址信息
};
转换成网络地址结构如下:
struct sockaddr_in ///网络地址结构
{
u_short sin_family; //地址族
u_short sin_port; //地址端口
struct in_addr sin_addr; //地址IP //"192.168.1.123"
char sin_zero[8]; //占位
};
其中sin_addr结构体:
struct in_addr
{
in_addr_t s_addr;
};
基于UDP实现的全双工通信
client.c
//使用多进程实现客户端的的收和发
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc , char *argv[])
{
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket error");
return 0;
}
char buf[1024];
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.85.128");
struct sockaddr_in srcaddr;
socklen_t srcaddrlen = sizeof(srcaddr);
pid_t pid = fork();
if(pid < 0)
{
perror("fork fail");
return 0;
}
if(pid > 0)
{
while(1)
{
printf("client >");
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
sendto(fd,buf,strlen(buf),0,(const struct sockaddr *)&seraddr,sizeof(seraddr));
if(strncmp(buf,"quit",4) == 0)
{
kill(pid,9);
wait(NULL);
return 0;
}
}
}
if(pid == 0)
{
while(1)
{
recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
printf(" %s\n",buf);
}
}
return 0;
}
server.c
//使用多进程实现服务端的收和发
#include<stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include<sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void handler(int signo)
{
wait(NULL);
kill(getpid(),9);
}
int main(int argc , char *argv[])
{
int fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0)
{
perror("socket error");
return 0;
}
char buf[1024];
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(50000);
seraddr.sin_addr.s_addr = inet_addr("192.168.85.128");
struct sockaddr_in srcaddr;
socklen_t srcaddrlen = sizeof(srcaddr);
bind(fd,(const struct sockaddr *)&seraddr,sizeof(seraddr));
recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr *) & srcaddr,&srcaddrlen);
if(strncmp(buf,"quit",4) == 0)
{
return 0;
}
printf("receive :%s",buf);
printf("receive from : %s\n",inet_ntoa(srcaddr.sin_addr));
printf("port : %d\n",ntohs(srcaddr.sin_port));
char returnbuf[2048];
pid_t pid = fork();
if(pid < 0)
{
perror("fork fail");
return 0;
}
if(pid > 0)
{
while(1)
{ //signal(SIGCHLD,handler);
//bzero(buf,sizeof(buf));
printf("server >");
fgets(buf,sizeof(buf),stdin);
sprintf(returnbuf,"%s%s","\nfrom server:",buf);
sendto(fd,returnbuf,strlen(returnbuf),0,(const struct sockaddr *)&srcaddr,sizeof(srcaddr));
}
}
if(pid == 0)
{
while(1)
{
recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr *) & srcaddr,&srcaddrlen);
if(strncmp(buf,"quit",4) == 0)
{
break;
}
printf("receive :%s",buf);
printf("receive from : %s\n",inet_ntoa(srcaddr.sin_addr));
printf("port : %d\n",ntohs(srcaddr.sin_port));
}
return 0;
}
}
二、TCP通信
由于TCP面向连接和可靠性高的特性,因此,在使用TCP协议通信时,需要在客户端和服务端之间先建立连接才能进行数据通信。
TCP通信流程
listen函数
listen
函数用于将一个套接字设置为监听模式,等待客户端的连接请求。
int listen(int sockfd, int backlog);
-
sockfd
: 套接字文件描述符。这个套接字必须已经用socket()
函数创建,并且使用bind()
函数绑定到了一个特定的地址和端口。 -
backlog
: 指定内核为该套接字排队的最大连接数。这表示在accept()
函数处理之前,内核可以排队等待的最大连接数。如果有更多的连接请求到达,新的连接可能会被拒绝或未处理,直到队列中有空闲位置。 - 成功时,返回
0
。 - 出错时,返回
-1
connect函数
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:该函数固定有客户端使用,表示从当前主机向目标
主机发起链接请求。
参数:sockfd 本地socket创建的套接子id
addr 远程目标主机的地址信息。
addrlen: 参数2的长度。
返回值:成功 0
失败 -1;
accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept
函数用于服务器端网络编程,在套接字处于监听模式时,accept
函数会从监听队列中提取第一个连接请求,并为该连接创建一个新的套接字(专门用来连接)
-
sockfd
: 监听套接字的文件描述符。这个套接字是通过socket()
创建的,并且已经通过bind()
绑定到了一个地址和端口,通过listen()
函数进入了监听状态。 -
addr
: 指向sockaddr
结构体的指针,accept
函数将填充客户端的地址信息到该结构体中。如果你不关心客户端的地址,可以将其设置为NULL
。 -
addrlen
: 一个值-结果参数,它指向一个保存了addr
结构大小的变量。在函数返回时,这个变量会包含客户端地址结构的实际大小。如果addr
为NULL
,则addrlen
也应为NULL
。 - 成功时,返回一个新的文件描述符,这个描述符代表新建立的连接。服务器通过这个新套接字与客户端进行通信。
- 出错时,返回
-1
,并设置errno
以指示错误类型。
这几个函数之间的关系
listen函数将创建的socket套接字设置为监听模式后,创建一个序列用来存储请求连接的设备,accept函数每次将第一个在队列中的请求进行处理后产生一个用来通信的和客户端建立连接的新的套接字 ,用来负责后续的通信