C语言实现跨主机通讯
文章目录
- c语音网络编程
- 实现TCP服务端与客户端通讯
- 客户端
- 服务端
- 一、代码功能概述
- 二、代码包含的头文件及宏定义
- 三、`main` 函数主体逻辑
- (1)创建套接字
- (2)绑定地址和端口
- (3)监听连接请求
- (4)接受客户端连接
- (5)数据交互循环
- (6)程序结束
- UDP实现指定ip端口号通讯
- 一、代码功能概述
- 二、代码包含的头文件及宏定义
- 三、`main`函数主体逻辑
- (1)创建套接字
- (2)设置套接字选项(地址复用)
- (3)绑定本地地址(可选操作在UDP客户端中并非必须)
- (4)指定对端服务器地址并“连接”(UDP逻辑上的连接)
- (5)数据接收与回复循环
- (6)程序结束
c语音网络编程
实现TCP服务端与客户端通讯
客户端
#include<myhead.h>
#define PORT 6666
#define IP "192.168.178.227"
#define handel_err(res,val) if(val==-1){perror(res);return-1;}
int main(int argc, const char *argv[])
{
int cfd=socket(AF_INET,SOCK_STREAM,0);
handel_err("socket",cfd);
struct sockaddr_in server={
.sin_family=AF_INET,
.sin_port=htons(PORT),
.sin_addr.s_addr=inet_addr(IP)
};
int res=connect(cfd,(struct sockaddr *)&server,sizeof(server));
handel_err("connect",res);
char buf[1024];
while(1){
bzero(buf,sizeof(buf));
printf(">>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
send(cfd,buf,sizeof(buf),0);
if(strcmp("#",buf)==0){
printf("退出\n");
break;
}
recv(cfd,buf,sizeof(buf),0);
printf("%s",buf);
}
return 0;
}
服务端
#include<myhead.h>
#define PORT 6666
#define IP "192.168.178.227"
#define handel_err(res,val) if(val==-1){perror(res);return-1;}
int main(int argc, const char *argv[])
{
int sfd=socket(AF_INET,SOCK_STREAM,0);
handel_err("socket",sfd);
struct sockaddr_in server={
.sin_family=AF_INET,
.sin_port=htons(PORT),
.sin_addr.s_addr=inet_addr(IP)
};
int res=bind(sfd,(struct sockaddr *)&server,sizeof(server));
handel_err("bind",res);
res=listen(sfd,10);
handel_err("listen",res);
struct sockaddr_in client;
socklen_t client_len=sizeof(client);
int cfd=accept(sfd,(struct sockaddr *)&client,&client_len);
char buf[1024];
handel_err("accept",cfd);
printf("success\n");
while(1){
bzero(buf,sizeof(buf));
int res=recv(cfd,buf,sizeof(buf),0);
printf("收到来自%s:%d->%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
if(strcmp("#",buf)==0||res==0){
printf("退出\n");
close(cfd);
break;
}
strcat(buf," ok");
send(cfd,buf,sizeof(buf),0);
}
return 0;
}
一、代码功能概述
这段C语言代码实现了一个简单的基于TCP协议的服务器端程序,它能够监听指定IP地址和端口上的客户端连接请求,接收客户端发送的数据,对数据进行简单处理(添加后缀 " ok")后再回发给客户端,直到客户端发送 “#” 字符或者连接断开(接收数据长度为0)时结束与该客户端的交互并关闭相应连接。
二、代码包含的头文件及宏定义
#include<myhead.h>
#define PORT 6666
#define IP "192.168.178.227"
#define handel_err(res,val) if(val==-1){perror(res);return-1;}
#include<myhead.h>
:#include <stdio.h> // 用于标准输入输出函数,像printf、scanf等的声明 #include <stdlib.h> // 包含如malloc、free等内存分配相关函数声明以及一些通用的实用函数声明 #include <string.h> // 提供字符串处理相关函数,比如strcpy、strcat、strcmp等的声明 #include <unistd.h> // 在类Unix系统中,有许多系统调用相关的函数声明,像write、read等 #include <sys/types.h> // 包含一些基本的系统数据类型定义,比如pid_t等 #include <sys/socket.h> // 用于套接字相关函数,像socket、bind、listen、accept等的声明,在你这段网络编程代码里很关键 #include <netinet/in.h> // 包含像struct sockaddr_in等网络地址相关结构体的定义以及一些网络字节序转换函数(如htons、ntohs等)的声明 #include <arpa/inet.h> // 有inet_addr、inet_ntoa等函数的声明,用于IP地址转换相关操作,代码里也有用到
#define PORT 6666
:定义了服务器监听的端口号为6666,后续在创建套接字、绑定地址等操作中会使用该端口号。#define IP "192.168.178.227"
:定义了服务器绑定的IP地址,也就是服务器将监听该IP地址对应的网络接口上的连接请求。#define handel_err(res,val) if(val==-1){perror(res);return-1;}
:这是一个宏定义,用于统一处理函数调用出错的情况。当函数返回值val
为 -1时,使用perror
函数输出错误信息(对应res
参数传入的函数名相关的错误提示)并直接返回 -1终止程序执行,方便进行错误处理。
三、main
函数主体逻辑
int main(int argc, const char *argv[])
{
// 以下为具体功能实现代码逻辑
}
(1)创建套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
handel_err("socket",sfd);
通过 socket
函数创建一个基于IPv4(AF_INET
)、面向连接的流式套接字(SOCK_STREAM
,对应TCP协议),创建成功后返回套接字描述符存放在 sfd
变量中。若创建失败(sfd
为 -1),则通过 handel_err
宏定义进行错误处理,输出错误信息并终止程序。
(2)绑定地址和端口
struct sockaddr_in server={
.sin_family=AF_INET,
.sin_port=htons(PORT),
.sin_addr.s_addr=inet_addr(IP)
};
int res=bind(sfd,(struct sockaddr *)&server,sizeof(server));
handel_err("bind",res);
- 首先构造了一个
struct sockaddr_in
类型的结构体server
,用于指定服务器要绑定的IP地址和端口信息。其中,将地址族设置为AF_INET
(IPv4),端口号通过htons
函数将主机字节序转换为网络字节序(因为网络通信中使用的是大端字节序,而主机字节序可能不同),IP地址通过inet_addr
函数将点分十进制的IP字符串转换为网络字节序的二进制形式。 - 然后使用
bind
函数将之前创建的套接字sfd
与服务器地址结构体server
进行绑定,如果绑定失败(res
为 -1),同样通过handel_err
宏进行错误处理。
(3)监听连接请求
res=listen(sfd,10);
handel_err("listen",res);
调用 listen
函数让服务器套接字进入监听状态,参数 10
表示允许等待连接的队列最大长度为10个,即最多可以有10个客户端的连接请求在队列中等待服务器处理。若监听失败(res
为 -1),则按错误处理逻辑进行处理。
(4)接受客户端连接
struct sockaddr_in client;
socklen_t client_len=sizeof(client);
int cfd=accept(sfd,(struct sockaddr *)&client,&client_len);
handel_err("accept",cfd);
printf("success\n");
- 定义了
struct sockaddr_in
类型的client
结构体用于存放客户端的地址信息,以及socklen_t
类型的client_len
变量记录客户端地址结构体的长度。 - 通过
accept
函数从监听套接字sfd
的连接请求队列中取出一个客户端连接请求,创建一个新的套接字描述符cfd
用于与该客户端进行后续的数据通信。如果接受连接失败(cfd
为 -1),进行错误处理,成功则输出 “success” 表示已成功接受客户端连接。
(5)数据交互循环
while(1){
bzero(buf,sizeof(buf));
int res=recv(cfd,buf,sizeof(buf),0);
printf("收到来自%s:%d->%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
if(strcmp("#",buf)==0||res==0){
printf("退出\n");
close(cfd);
break;
}
strcat(buf," ok");
send(cfd,buf,sizeof(buf),0);
}
- 进入一个无限循环,在每次循环中:
- 首先使用
bzero
函数将用于接收数据的缓冲区buf
清零,防止之前的数据残留影响本次接收。 - 调用
recv
函数从与客户端通信的套接字cfd
中接收数据,将接收到的数据存放在buf
中,并获取接收数据的长度存放在res
变量中。同时输出接收到的数据以及客户端的IP地址和端口信息。 - 通过判断接收到的数据是否为 “#” 或者接收数据长度
res
是否为0来决定是否退出循环。如果是,表示客户端请求结束连接或者连接已断开,此时输出 “退出”,关闭与该客户端通信的套接字cfd
并跳出循环。 - 如果不是结束条件,则对接收的数据进行处理,通过
strcat
函数在数据后面添加 " ok" 后缀,然后使用send
函数将处理后的数据回发给客户端。
- 首先使用
(6)程序结束
return 0;
当与客户端的数据交互循环结束后,整个 main
函数执行完毕,返回0表示程序正常结束。
UDP实现指定ip端口号通讯
#include<myhead.h>
#define PORT 6666
#define IP "192.168.178.227"
#define handel_err(res,val) if(val==-1){perror(res);return-1;}
int main(int argc, const char *argv[])
{
int sfd=socket(AF_INET,SOCK_DGRAM,0);
handel_err("socket",sfd);
struct sockaddr_in server={
.sin_family=AF_INET,
.sin_port=htons(PORT),
.sin_addr.s_addr=inet_addr(IP)
};
int n=1;
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&n,4);
int res=bind(sfd,(struct sockaddr*)&server,sizeof(server));
handel_err("bind",res);
struct sockaddr_in client={
.sin_family=AF_INET,
.sin_port=htons(8888),
.sin_addr.s_addr=inet_addr("192.168.178.15")
};
res=connect(sfd,(struct sockaddr *)&client,sizeof(client));
char buf[1024];
while(1){
bzero(buf,sizeof(buf));
int res=recv(sfd,buf,sizeof(buf),0);
printf("收到->%s\n",buf);
if(res==0){
printf("下线\n");
break;
}
send(sfd,"ok\n",sizeof("ok\n"),0);
}
return 0;
}
一、代码功能概述
这段C语言代码实现了一个基于UDP协议的客户端程序。它能够向指定的服务器IP地址和端口发送连接请求(虽然UDP是无连接的,但这里使用connect
函数指定了对端信息方便后续收发操作),接收来自服务器端发送的数据,当接收到的数据长度为0时,表示连接断开(在UDP里可视为服务器不再发送数据等情况),此时程序会输出“下线”并结束运行,在接收到正常数据后会向服务器回复“ok\n”。
二、代码包含的头文件及宏定义
#include<myhead.h>
#define PORT 6666
#define IP "192.168.178.227"
#define handel_err(res,val) if(val==-1){perror(res);return-1;}
#include<myhead.h>
:#include <stdio.h> // 用于标准输入输出函数,像printf、scanf等的声明 #include <stdlib.h> // 包含如malloc、free等内存分配相关函数声明以及一些通用的实用函数声明 #include <string.h> // 提供字符串处理相关函数,比如strcpy、strcat、strcmp等的声明 #include <unistd.h> // 在类Unix系统中,有许多系统调用相关的函数声明,像write、read等 #include <sys/types.h> // 包含一些基本的系统数据类型定义,比如pid_t等 #include <sys/socket.h> // 用于套接字相关函数,像socket、bind、listen、accept等的声明,在你这段网络编程代码里很关键 #include <netinet/in.h> // 包含像struct sockaddr_in等网络地址相关结构体的定义以及一些网络字节序转换函数(如htons、ntohs等)的声明 #include <arpa/inet.h> // 有inet_addr、inet_ntoa等函数的声明,用于IP地址转换相关操作,代码里也有用到 ```
#define PORT 6666
:定义了一个宏常量,用于表示服务器监听的端口号,后续在构建服务器地址结构体等操作中会使用该端口值来确定通信端口。#define IP "192.168.178.227"
:同样是宏定义,指定了服务器的IP地址,是客户端要与之通信的目标服务器的网络地址标识。#define handel_err(res,val) if(val==-1){perror(res);return-1;}
:这是一个用于错误处理的宏定义。当函数调用返回值val
为 -1时,会通过perror
函数输出对应res
(函数名相关)的错误提示信息,并直接返回 -1终止程序,有助于简化代码中重复的错误处理逻辑。
三、main
函数主体逻辑
int main(int argc, const char *argv[])
{
// 以下为具体功能实现代码逻辑
}
(1)创建套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
handel_err("socket",sfd);
使用socket
函数创建了一个基于IPv4(AF_INET
)的、面向无连接的数据报套接字(SOCK_DGRAM
,对应UDP协议),创建成功后将套接字描述符赋值给sfd
变量。若创建套接字操作失败(sfd
等于 -1),则通过handel_err
宏定义进行错误处理,输出错误提示并终止程序。
(2)设置套接字选项(地址复用)
int n=1;
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&n,4);
通过setsockopt
函数设置套接字选项,这里将sfd
对应的套接字设置为允许地址复用(SO_REUSEADDR
选项),其作用通常在于允许程序在短时间内重新绑定到相同的本地地址和端口,避免因上一次连接关闭后端口还处于TIME_WAIT
状态而无法立即重用该端口的问题。参数中的SOL_SOCKET
表示操作针对套接字层,&n
是设置的值(这里将n
设为1表示启用该选项),4
表示选项值的长度(这里因为int
类型在常见系统中占4字节)。
(3)绑定本地地址(可选操作在UDP客户端中并非必须)
struct sockaddr_in server={
.sin_family=AF_INET,
.sin_port=htons(PORT),
.sin_addr.s_addr=inet_addr(IP)
};
int res=bind(sfd,(struct sockaddr*)&server,sizeof(server));
handel_err("bind",res);
- 首先构建了一个
struct sockaddr_in
类型的结构体server
,用于指定本地地址相关信息,包括设置地址族为AF_INET
(IPv4),将端口号通过htons
函数转换为网络字节序(因为网络通信按大端字节序传输端口号等信息,主机字节序可能不同),IP地址通过inet_addr
函数将点分十进制的IP字符串转换为网络字节序的二进制形式。 - 接着使用
bind
函数尝试将创建的套接字sfd
绑定到上述指定的本地地址结构体server
上。虽然在UDP客户端程序中,绑定操作并非强制要求,但在一些特定场景下(比如需要固定客户端使用某个本地端口等情况)可以进行此操作。若绑定失败(res
为 -1),会按照handel_err
宏定义的逻辑处理错误,输出提示并结束程序。
(4)指定对端服务器地址并“连接”(UDP逻辑上的连接)
struct sockaddr_in client={
.sin_family=AF_INET,
.sin_port=htons(8888),
.sin_addr.s_addr=inet_addr("192.168.178.15")
};
res=connect(sfd,(struct sockaddr *)&client,sizeof(client));
- 创建了另一个
struct sockaddr_in
类型的结构体client
,用于表示要通信的对端服务器的地址信息,同样设置地址族为AF_INET
,将对端服务器的端口号(这里设置为8888)转换为网络字节序,以及将对端服务器的IP地址(“192.168.178.15”)转换为网络字节序的二进制形式。 - 调用
connect
函数,虽然UDP是无连接协议,但此处使用connect
函数可以将后续的recv
和send
操作默认针对这个指定的对端地址进行,相当于逻辑上建立了一个与该服务器的“连接”,方便后续的数据收发操作。
(5)数据接收与回复循环
char buf[1024];
while(1){
bzero(buf,sizeof(buf));
int res=recv(sfd,buf,sizeof(buf),0);
printf("收到->%s\n",buf);
if(res==0){
printf("下线\n");
break;
}
send(sfd,"ok\n",sizeof("ok\n"),0);
}
- 首先定义了一个大小为1024字节的字符数组
buf
作为接收数据的缓冲区。 - 进入一个无限循环,在每次循环中:
- 先使用
bzero
函数将缓冲区buf
清零,避免之前的数据残留影响本次接收结果。 - 调用
recv
函数从之前通过connect
指定的对端服务器套接字sfd
接收数据,接收到的数据存放在buf
中,同时返回接收数据的长度存放在res
变量中,并输出接收到的数据内容(使用printf
)。 - 通过判断接收数据的长度
res
是否为0来决定是否结束循环。如果res
等于0,表示可能服务器端已停止发送数据或者连接出现异常断开等情况,此时输出“下线”提示信息并跳出循环,结束程序运行。 - 若接收到的数据长度不为0,即接收到了正常的数据,则调用
send
函数向对端服务器发送字符串“ok\n”作为回复,告知服务器端已成功接收数据等信息。
- 先使用
(6)程序结束
return 0;
当数据接收与回复的循环结束后(即出现连接断开等情况),main
函数执行完毕,通过返回0表示程序正常结束。