一、基础通信
1.TCP通信架构
对于服务器,其通信流程一般有如下步骤:
?? ?1.调用socket函数创建socket,这一步会创建一个文件描述符fd。 ?? ?2.调用bind函数将socket(也就是fd)绑定到某个ip和端口的二元组上。 ?? ?3.调用listen函数开启侦听端口。 ?? ?4.调用accept阻塞等待接受连接,当有客户端请求连接上来后,
产生一个新的socket(客户端socket)。 ?? ?5.基于新产生的socket调用send或recv函数开始与客户端进行数据通信。 ?? ?6.通信结束后,调用close函数关闭客户端socket。 对于客户端,其通信流程一般有如下步骤:
?? ?1.调用socket函数创建客户端socket。 ?? ?2.调用connect函数尝试连接服务器。 ?? ?3.连接成功以后调用send或recv函数开始与服务器进行数据通信。 ?? ?4.通信结束后,调用close函数关闭socket。
其流程图如下所示:
?服务器端代码:??
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <pthread.h>
#include <stdlib.h>
#include "common.h"
struct thread_msg {
int cli_socket;
pthread_t tid;
};
void *serv_for_client(void *args)
{
int ret;
struct thread_msg *pmsg = args;
int socket = pmsg->cli_socket;
struct msg_struct serv_msg;
while(1){
memset(&serv_msg,0,sizeof(serv_msg));
ret = recv(socket,&serv_msg,sizeof(serv_msg),0);
if(ret<0){
perror("recv err"); close(socket); free(pmsg); return NULL;
}else if(ret == 0){
printf("serv found peer shutdwon\n"); close(socket); free(pmsg);
return NULL;
}
printf("serv got msg:temp%d %d %d cnt%d des:%s\n",\
serv_msg.temp,serv_msg.humidity,serv_msg.wind,\
serv_msg.sendcnt,serv_msg.des);
serv_msg.serv_response++;
ret = send(socket,&serv_msg,sizeof(serv_msg),0);
if(ret<0){
perror("send err"); return NULL;
}
}
close(socket);
return NULL;
}
int main()
{
/*
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 域,范围,区域
AF_UNIX,: linux进程内部通信
AF_INET ,用于ipv4通信
AF_INET6,用于ipv6通信
type:
SOCK_STREAM, TCP专属,提供面向连接 字节流 安全可靠服务
SOCK_DGRAM, UDP专属
SOCK_RAW , 自己实现一套接口
protocol: 为0即可
返回值:
正数--新的连接socket
-1: errno
*/
int socket_main = socket(AF_INET,SOCK_STREAM, 0 );
if(socket_main <0){
perror("create main socket err");
return -3;
}
/*
给main socket指定服务器自己的ip和端口号
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
addr:应该包含ip和端口号
我们没有使用这个类型
struct sockaddr {
sa_family_t sa_family; //地址族,采用AF_xxx的形式,如:AF_INET
char sa_data[14]; //14字节的特定协议地址
}
编程中一般不针对 sockaddr 数据结构操作,而是使用与 sockaddr 等价的sockaddr_in 数据结构
我们使用的是下面的类型:
struct sockaddr_in { man 7 ip
sa_family_t sin_family; 永远等于AF_INET. address family: AF_INET
in_port_t sin_port; 端口号以大端方式存放的 port in network byte order
struct in_addr sin_addr; internet address IP地址
};
Internet address.
struct in_addr {
uint32_t s_addr; IP地址,以大端方式存放 address in network byte order
};
addrlen: addr结构体长度
返回值:
0-success
-1: errno
*/
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10086); //host to net short
/* int inet_addr(char *ip)
负责将字符串表示的 点分法,转换为 u32,以大端方式存放
*/
serv_addr.sin_addr.s_addr = INADDR_ANY; //inet_addr("192.168.3.197"); 将a.b.c.d形式的IP转换为32位的IP
//将一个点分十进制的IP转换成一个长整型数(u_long类型)等同于inet_addr()。
/* 如果你指定ip地址,改程序只能跑在指定服务器上, 如何让服务器自动获取本地ip
serv_addr.sin_addr.s_addr = 0; bind函数发现ip为0,自动获取本地ip
*/
int ret = bind(socket_main,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret<0){
perror("bind err");
return -34;
}
/*监听整个网络
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
backlog: 主socket在一瞬间可以同时处理的客户端个数
一般选5,表示可以同时处理2*5+1
返回:
0-success
-1:errno
*/
ret = listen(socket_main,5);
if(ret<0){
perror("listen err");
return -3;
}
/*
等待客户端连接-----阻塞等待
#include <sys/types.h>
#include <sys/socket.h>
int accept(int main_sockfd, struct sockaddr *addr, socklen_t *addrlen);
main_sockfd:主socket,用于监听网络,等待客户端连接
addr: 客户端连接上来之后,客户端的ip+port存放在addr
真实的类型是 struct sockaddr_in
struct sockaddr_in { man 7 ip
sa_family_t sin_family; 永远等于AF_INET. address family: AF_INET
in_port_t sin_port; 端口号以大端方式存放的 port in network byte order
struct in_addr sin_addr; internet address
};
Internet address.
struct in_addr {
uint32_t s_addr; IP地址,以大端方式存放 address in network byte order
};
addrlen: 双向参数 , 对方地址的长度
调用者(你)告诉accept存放地址的buf有多长.
accept获取对方地址之后,会告诉真实的长度
返回值:
正数: 一个新的socket,用于和客户端进行通信
-1:errno
*/
struct sockaddr_in cli_addr;
socklen_t addrlen=sizeof(cli_addr);
struct thread_msg *pmsg =NULL;
int socket_new;
loop:
socket_new = accept(socket_main ,(struct sockaddr *)&cli_addr, &addrlen );
if(socket_new<0){
perror("accept err");
return -3;
}
printf("serv accept client ip=%s port=%d\n",\
inet_ntoa (cli_addr.sin_addr ), ntohs(cli_addr.sin_port) );
// inet_ntoa ,将32位IP转换为a.b.c.d的格式
pmsg = malloc(sizeof(*pmsg));
pmsg->cli_socket = socket_new;
pthread_create(&pmsg->tid,NULL, serv_for_client , pmsg );
//处理完毕一个连接,然后继续去等待新客户端
goto loop;
close(socket_main); //一般最后才能关闭
return 0;
}
客户端代码为:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
int main()
{
int socket_cli = socket(AF_INET,SOCK_STREAM, 0 );
if(socket_cli <0){
perror("create client socket err");
return -3;
}
/*
//int connect(int socket_cli, serv_ip,serv_port);
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr_in {
sa_family_t sin_family; 永远等于AF_INET. address family: AF_INET
in_port_t sin_port; 端口号以大端方式存放的 port in network byte order
struct in_addr sin_addr; internet address
};
Internet address.
struct in_addr {
uint32_t s_addr; IP地址,以大端方式存放 address in network byte order
};
成功返回 0
失败 -1,errno
*/
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10086);
serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197");
int ret = connect(socket_cli,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("connect err");
return -3;
}
struct msg_struct cli_msg;
while(1){
cli_msg.temp=45;
cli_msg.humidity=40;
cli_msg.wind=10;
strcpy(cli_msg.des,"today sun day");
ret = send(socket_cli,&cli_msg,sizeof(cli_msg),0);
if(ret<0){
perror("send err");
return -34;
}
memset(&cli_msg,0,sizeof(cli_msg));
ret = recv(socket_cli,&cli_msg,sizeof(cli_msg),0);
if(ret<0){
perror("recv err");
return -34;
}else if(ret ==0){
printf("cli found serv shutdown\n");
return 0;
}
printf("cli got msg:response=%d\n",cli_msg.serv_response);
sleep(1);
}
}
头文件为:common.h
#ifndef COMM_HH
#define COMM_HH
struct msg_struct {
char temp;
int humidity;
int wind;
int sendcnt;
int serv_response;
char des[32];
};
#endif
2.UDP通信
udp通信架构:
? ? ? ? 对于udp通信没有严格的服务器和客户端的说法,谁先发送,谁就是发送端。 也没有没了 listen? accept 和 conect的这些操作,它天生支持并发。
流程图如下:
服务器端代码为:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include "common.h"
int main()
{
/*
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 域,范围,区域
AF_UNIX,: linux进程内部通信
AF_INET ,用于ipv4通信
AF_INET6,用于ipv6通信
type:
SOCK_STREAM, TCP专属,提供面向连接 字节流 安全可靠服务
SOCK_DGRAM, UDP专属
SOCK_RAW , 自己实现一套接口
protocol: 为0即可
返回值:
正数--新的连接socket
-1: errno
*/
int socket_serv = socket(AF_INET,SOCK_DGRAM, 0 );
if(socket_serv <0){
perror("create main socket err");
return -3;
}
/*
给main socket指定服务器自己的ip和端口号
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
addr:应该包含ip和端口号
我们没有使用这个类型
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
我们使用的是下面的类型:
struct sockaddr_in { man 7 ip
sa_family_t sin_family; 永远等于AF_INET. address family: AF_INET
in_port_t sin_port; 端口号以大端方式存放的 port in network byte order
struct in_addr sin_addr; internet address
};
Internet address.
struct in_addr {
uint32_t s_addr; IP地址,以大端方式存放 address in network byte order
};
addrlen: addr结构体长度
返回值:
0-success
-1: errno
*/
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10096); //host to net short
/* int inet_addr(char *ip)
负责将字符串表示的 点分法,转换为 u32,以大端方式存放
*/
serv_addr.sin_addr.s_addr = INADDR_ANY; //inet_addr("192.168.3.197");
/* 如果你指定ip地址,改程序只能跑在指定服务器上, 如何让服务器自动获取本地ip
serv_addr.sin_addr.s_addr = 0; bind函数发现ip为0,自动获取本地ip
*/
int ret = bind(socket_serv,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret<0){
perror("bind err");
return -34;
}
char txbuf[128];
struct msg_struct recvmsg;
while(1){
/*
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
socketfd:通信的手机/套接字
void *buf, size_t len ,接收数据存放的buf,和你想要接收的长度
flags: 0
src_addr: 对方的ip和端口号
addrlen: 双向参数,你告诉它地址buf有多长,对方告诉你真实的长度
返回值:
正值--表示真实接收的字节数
-1: errno
*/
struct sockaddr_in cli_addr;
socklen_t addrlen=sizeof(cli_addr);
memset(&recvmsg,0,sizeof(recvmsg));
ret = recvfrom(socket_serv,&recvmsg,sizeof(recvmsg),0, (struct sockaddr *)&cli_addr, &addrlen);
if(ret<0){
perror("recvfrom err");
return -34;
}
printf("ser cli ip=%s %d temp%d %d %d cnt%d des:%s\n",
inet_ntoa (cli_addr.sin_addr ), ntohs(cli_addr.sin_port),\
recvmsg.temp,recvmsg.humidity,recvmsg.wind,recvmsg.sendcnt,recvmsg.des);
/*
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
返回值:
正数--真实发送的长度
-1:errno
*/
strcpy(txbuf,"hello cli,Your msg be got.");
ret = sendto( socket_serv, txbuf,strlen(txbuf), 0, (struct sockaddr *)&cli_addr, sizeof(cli_addr) );
if(ret<0){
perror("sendto err");
return -34;
}
}
}
客户端代码为:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include "common.h"
int main()
{
int ret;
int socket_cli = socket(AF_INET,SOCK_DGRAM, 0 );
if(socket_cli <0){
perror("create main socket err");
return -3;
}
/*
客户端其实是可以bind的,但是一般我们都不用
因为在sendto函数中,如果没有bind,会自动帮你分配一个
*/
char rxbuf[128];
struct msg_struct cli_msg;
memset(&cli_msg,0,sizeof(cli_msg));
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10096);
serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197");
while(1){
cli_msg.temp=43;
cli_msg.humidity = 40;
cli_msg.wind = 3;
strcpy(cli_msg.des,"sun day");
cli_msg.sendcnt++;
ret = sendto( socket_cli,&cli_msg,sizeof(cli_msg),0,(struct sockaddr *)&serv_addr, sizeof(serv_addr) );
if(ret<0){
perror("sendto err");
return -34;
}
socklen_t addrlen = sizeof(serv_addr);
memset(&cli_msg,0,sizeof(cli_msg));
ret = recvfrom(socket_cli,&cli_msg,sizeof(cli_msg),0, (struct sockaddr *)&serv_addr, &addrlen);
if(ret<0){
perror("recvfrom err");
return -34;
}
printf("cli got serv: response=%d\n", cli_msg.serv_response );
sleep(1);
}
close(socket_cli);
}
总结
这里也讲到了tcp和udp的区别(12条消息) 第三十四篇,网络编程UDP通信过程实现和函数接口_肖爱Kun的博客-CSDN博客_udp通信过程https://blog.csdn.net/weixin_44651073/article/details/124225375?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166269910816782427467151%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=166269910816782427467151&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-124225375-null-null.142^v47^control_1,201^v3^control_2&utm_term=udp%E9%80%9A%E4%BF%A1%E6%B5%81%E7%A8%8B&spm=1018.2226.3001.4187如果有什么错误的地方,欢迎在评论区留言
|