使用Netty来完成Spring Boot和C语言客户端的通信。下面将会分别介绍UDP和TCP。TCP只介绍Java的部分,C语言的部分不说。
一、C语言客户端和服务端的实现
- 客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){
int sockfd;
int len;
struct sockaddr_in address;
int result;
char str[30];
char* ch = str;
while(1){
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(6678);
len = sizeof(address);
printf("请输入你要传输的数据\n");
gets(ch);
len = strlen(ch);
sendto(sockfd, ch, len, 0,(struct sockaddr *)&address, sizeof(address));
}
close(sockfd);
exit(0);
return 0;
}
- 服务端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(){
int sockfd;
int len;
struct sockaddr_in address;
int result;
char str[300];
char* ch = str;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(5678);
bind(sockfd, (struct sockaddr *)&address, sizeof(address));
int nRecvBuf=32768;
setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
while(1){
//接受输入
printf("等待接收:\n");
socklen_t lent = sizeof(address);
result = recvfrom(sockfd, ch, 8192, 0, (struct sockaddr *)&address, &lent);
ch[result] = '\0';
printf("收到回复:%s\n", ch);
}
close(sockfd);
exit(0);
return 0;
}
二、Netty的依赖导入
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
三、UDP的服务端实现
- 消息处理类
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import lombok.extern.log4j.Log4j2;
/**
* 对于新消息进行操作的类
*/
@Log4j2
public class NettyUDPServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
/**
* 重写接收到的数据的具体操作
* @param channelHandlerContext
* @param datagramPacket
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket) throws Exception {
System.out.println("收到数据:" + datagramPacket.content().toString(CharsetUtil.UTF_8) );
}
/**
* 出错回调
* @param channelHandlerContext
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {
cause.printStackTrace();
channelHandlerContext.close();
}
}
- 客户端类
import com.example.demo.Servers.CServerListen;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOptipackage com.example.demo.Netty.UDP;
on;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP的服务端类
*/
@Service
@Log4j2
public class UDPServer {
private Bootstrap bootstrap;
private NioEventLoopGroup group; //工作组
private Channel channel; //信道
private int port;
private String address;
/**
* 线程的具体执行内容
*/
class Listen implements Runnable {
@Override
public void run(){
Thread.currentThread().setName("等待c客户端的消息进程");
System.out.println("正在监听" + port + "端口,等待客户端消息。");
try {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.option(ChannelOption.SO_RCVBUF, 1024 * 1024 * 100)
.handler(new NettyUDPServerHandler());
channel = bootstrap.bind(address, port).sync().channel();
channel.closeFuture().await();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
}
/**
* 启动服务端
*/
public void start() {
Listen ser = new Listen();
Thread m = new Thread(ser);
m.start();
}
/**
* 设定端口和ip
* @param address
* @param port
* @return
*/
public UDPServer bind(String address, int port) {
this.address = address;
this.port = port;
return this;
}
}
四、UDP的客户端实现
- 客户端类
import com.example.demo.Netty.NettyClientInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.net.InetSocketAddress;
@Service
@Log4j2
public class UDPClient {
private int port;//绑定的端口
private String address;//绑定的ip
private int aim_port;//目标的端口
private String aim_address;//目标的ip
/**
* 绑定目标和本地的ip以及端口
* @param port
* @param address
* @param aim_port
* @param aim_address
* @return
*/
public UDPClient bind(int port, String address, int aim_port, String aim_address){
this.address = address;
this.port = port;
this.aim_address = aim_address;
this.aim_port = aim_port;
return this;
}
/**
* 发送消息
* @param data
*/
public void send(String data) {
EventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
Bootstrap clientBoostrap = new Bootstrap();
clientBoostrap.group(eventExecutors)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
//接收缓冲区大小设置
.option(ChannelOption.SO_RCVBUF, 1024 * 1024 * 100)
//发送缓冲区大小设置
.option(ChannelOption.SO_SNDBUF, 1024 * 1024 * 100)
.handler(new NettyUDPClientHandler());
//绑定本地的ip和端口
Channel channel = clientBoostrap.bind(address, port).sync().channel();
//发送消息
channel.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer(data, CharsetUtil.UTF_8),
new InetSocketAddress(aim_address, aim_port))).sync();
System.out.println("通道id:" + channel.id().toString());
//等待5秒后,关闭
channel.closeFuture().await(5000);
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("客户端关闭了");
eventExecutors.shutdownGracefully();
}
}
}
- 消息处理类
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.log4j.Log4j2;
/**
* 操作发送消息之后,接收的消息操作类
*/
@Log4j2
public class NettyUDPClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {
/**
* 操作接收到的消息
* @param channelHandlerContext
* @param datagramPacket
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket) throws Exception {
System.out.println(datagramPacket.content().toString());
// channelHandlerContext.close();
}
/**
* 通道读取完毕回调
* @param channelHandlerContext
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {
super.channelReadComplete(channelHandlerContext);
System.out.println("关闭channel");
channelHandlerContext.close();
}
/**
* 出错回调
* @param channelHandlerContext
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {
cause.printStackTrace();
channelHandlerContext.close();
}
}
五、TCP的服务端实现
- 服务端类
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
@Slf4j
public class NettyServer {
public void start(InetSocketAddress socketAddress){
//主线程,先创建一个
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//工作线程
EventLoopGroup workGroup = new NioEventLoopGroup(200);
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer())
.localAddress(socketAddress)
//设置队列大小
.option(ChannelOption.SO_BACKLOG, 1024)
//两小时没有通讯,发送探测数据报文
.childOption(ChannelOption.SO_KEEPALIVE, true);
try {
ChannelFuture future = bootstrap.bind(socketAddress).sync();
System.out.println("服务器开始监听端口:" + socketAddress.getPort());
future.channel().closeFuture().sync();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
//关闭两个部分工作组
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
- 初始化类
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 添加解码和编码
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}
- 消息处理类
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端连接会触发
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("通道激活");
}
/**
* 客户端发消息会触发
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到消息" + msg.toString());
ctx.write("你好啊");
ctx.flush();
}
/**
* 发生异常触发
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
六、TCP的客户端实现
- 客户端类
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class NettyClient {
public void start() {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap()
.group(group)
.option(ChannelOption.TCP_NODELAY, true)
.channel(NioSocketChannel.class)
.handler(new NettyClientInitializer());
try {
ChannelFuture future = bootstrap.connect("127.0.0.1", 5678).sync();
System.out.println("客户端尝试连接");
future.channel().closeFuture().sync();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
}
- 初始化类
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("decoder", new StringDecoder());
socketChannel.pipeline().addLast("encoder", new StringEncoder());
socketChannel.pipeline().addLast(new NettyClientHandler());
}
}
- 消息处理类
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端活动");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客户端收到消息" + msg.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
七、设置Spring Boot启动时开始启动Netty的服务端或者客户端
import com.example.demo.Netty.UDP.UDPClient;
import com.example.demo.Netty.UDP.UDPServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
/**
* 启动Spring boot 开始等待客户端消息
*/
@Component
public class StartWith implements ServletContextAware {
UDPServer udpServer;
UDPClient udpClient;
@Autowired
public void setUdpClient(UDPClient udpClient) {
this.udpClient = udpClient;
}
@Autowired
public void setUdpServer(UDPServer udpServer) {
this.udpServer = udpServer;
}
@Override
public void setServletContext(ServletContext servletContext){
//发送
// udpClient.bind(6679,"127.0.0.1",5678,"127.0.0.1")
// .send("你好啊,这次没问题了吧?");
//接收
udpServer.bind("127.0.0.1", 6678)
.start();
}
}
八、注意事项
- UDP的部分,我全部都自己测试过,没有问题,TCP的部分没有测试。
- UDP的Java部分采用了构建者模式,线程启动也比较重要,否则Spring Boot的主线程会被阻塞。