C语言实现跨主机通讯

文章目录

c语音网络编程

实现TCP服务端与客户端通讯

客户端

c 复制代码
#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;
}

服务端

c 复制代码
#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)时结束与该客户端的交互并关闭相应连接。

二、代码包含的头文件及宏定义

c 复制代码
#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>

    c 复制代码
    #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 函数主体逻辑

c 复制代码
int main(int argc, const char *argv[])
{
    // 以下为具体功能实现代码逻辑
}
(1)创建套接字
c 复制代码
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)绑定地址和端口
c 复制代码
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)监听连接请求
c 复制代码
res=listen(sfd,10);
handel_err("listen",res);

调用 listen 函数让服务器套接字进入监听状态,参数 10 表示允许等待连接的队列最大长度为10个,即最多可以有10个客户端的连接请求在队列中等待服务器处理。若监听失败(res 为 -1),则按错误处理逻辑进行处理。

(4)接受客户端连接
c 复制代码
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)数据交互循环
c 复制代码
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)程序结束
c 复制代码
return 0;

当与客户端的数据交互循环结束后,整个 main 函数执行完毕,返回0表示程序正常结束。

UDP实现指定ip端口号通讯

c 复制代码
#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"。

二、代码包含的头文件及宏定义

c 复制代码
#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>

    c 复制代码
    	#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函数主体逻辑

c 复制代码
int main(int argc, const char *argv[])
{
    // 以下为具体功能实现代码逻辑
}
(1)创建套接字
c 复制代码
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)设置套接字选项(地址复用)
c 复制代码
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客户端中并非必须)
c 复制代码
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逻辑上的连接)
c 复制代码
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函数可以将后续的recvsend操作默认针对这个指定的对端地址进行,相当于逻辑上建立了一个与该服务器的"连接",方便后续的数据收发操作。
(5)数据接收与回复循环
c 复制代码
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)程序结束
c 复制代码
return 0;

当数据接收与回复的循环结束后(即出现连接断开等情况),main函数执行完毕,通过返回0表示程序正常结束。

相关推荐
To_再飞行28 分钟前
K8s 调度管理
linux·云原生·kubernetes
2302_799525741 小时前
【Hadoop】Hadoop集群安装中出现的问题
linux·hadoop
刘一说1 小时前
Linux调试命令速查:Java/微服务必备
java·linux·微服务
枫の准大一1 小时前
【Linux游记】基础指令篇
linux
ypf52081 小时前
OrbStack 配置国内镜像加速
linux
Hello.Reader1 小时前
一文通关 Proto3完整语法与工程实践
java·linux·数据库·proto3
Hello.Reader1 小时前
一文吃透 Protobuf “Editions” 模式从概念、语法到迁移与实战
linux·服务器·网络·protobuf·editions
陌上花开缓缓归以2 小时前
linux ubi文件系统
linux
口嗨农民工2 小时前
exiftool 分析jpeg图片使用
linux
大明者省2 小时前
pycharm解释器使用anaconda建立的虚拟环境里面的python,无需系统里面安装python。
linux·python·pycharm