socket通信

来源:互联网 发布:java通讯框架 编辑:程序博客网 时间:2024/06/18 12:13
      前段时间学习了通信机制,小组做了一个仿QQ的聊天工具,能登录,注册,加好友,私聊,群聊,能玩通信游戏。我完成的任务一个你画我猜的通信游戏的模块,并完成了测试。
     本文主要叙述socket的通信机制,关于你画我猜这个模块后续再写出来,这里贴的代码是我完成的模块代码中截取出来的,只为了体现逻辑思路,连贯性可能欠缺请见谅。
     我们在局域网下进行socket通信,首先建立一个本机的服务器,监听端口,等待访问,当有客户端访问时,交给服务端线程处理;然后当客户端对象访问服务器时,客户端线程处理客户端的操作,通过TCP/ip协议与服务端通信。

     下面先介绍一些术语:
     TCP/IP协议是一种面向连接的,可靠的网络传输协议,比如你给别人打电话,必须等线路接通了、对方拿起话筒才能相互通话。而UDP协议是非面向连接的协议,就是在正式通信前不必与对方先建立连接,例如你在发短信的时候,只需要输入对方手机号就OK了。
      一个TCP连接必须要经过三次“对话”才能建立起来,这三次对话的简单过程:主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。
      我们要确认网络上的每一台计算机,靠的就是能唯一标识该计算机的网络地址,这个地址就叫做IP。在Internet里,IP地址是一个32位的二进制地址,为了便于记忆,将它们分为4组,每组8位,由小数点分开,用四个字节来表示,而且,用点分开的每个字节的数值范围是0~255,如202.116.0.1。
       socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。

      接下来用代码进行说明:
     1、服务端:首先建立一个ServerSocket服务器端口号,当有客户端访问时,交给服务端线程处理。

public class Server {//服务端public void setup(int port){    try {    //绑定服务器端口号ServerSocket sers = new ServerSocket(port);System.out.println("服务器监听端口"+port+"成功!");while(true){//等待客户端访问Socket socket = sers.accept();System.out.println("有人访问!");//把客户端交给线程处理SocketThread st = new SocketThread(socket);st.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {new Server().setup(6666);}

    服务端线程:得到连接入socket的一个输入输出流,Output写数据送到客户机,Input读取数据到服务器,自定义通信协议,根据接受数据包的不同类型进行不同处理。线程类中的run方法以及部分想客户端发送消息的方法如下,更多的向客户端发送不同类型数据的方法不再赘述:
public void run() {try {// 读取客户端发送的消息input = socket.getInputStream();DataInputStream dis = new DataInputStream(input);// 向客户端发送的消息output = socket.getOutputStream();dos = new DataOutputStream(output);// 输入名字String str = "请输入你的名字:\r\n";// 服务器向客户端发送消息sendMessage(str);// 读取客户端输入的名字name = readLine(input);String name2 = name + "(" + socket.getInetAddress() + ")";System.out.println("name:" + name2);// 从客户端读取字符串消息while (true) {// 接受数据包的类型int type = dis.readInt();if (type == 1) {// 接收数据包的长度int len = dis.readInt();byte[] bytes = new byte[len];dis.readFully(bytes);// 读取客户端的输入流字符串String line = new String(bytes, "GBK");if ("bye\n".equals(line)) {System.out.println("服务器收到 " + name + "已下线!");break;}// 打印当前客户所说的话System.out.println("服务器收到 " + name + ":" + line + "======");                    System.out.println(keyWord+"\n======");if (line.equals(keyWord + "\n")) {//判断对方是否猜对System.out.println("猜对了!");for (int i = 0; i < list.size(); i++) {SocketThread st = list.get(i);// 向其他客户端发出消息st.sendMessage(name + "猜对了!\n");st.sendAccess("good");}sendTitle();}// 群发消息for (int i = 0; i < list.size(); i++) {SocketThread st = list.get(i);if (st == this) {continue;}// 向其他客户端发出消息String msg = name + ":" + line;st.sendMessage(msg);}} else if (type == 2) {// 接收画图信息int len = dis.readInt();int x1 = dis.readInt();int y1 = dis.readInt();int x2 = dis.readInt();int y2 = dis.readInt();int r = dis.readInt();int g = dis.readInt();int b = dis.readInt();Color c = new Color(r,g,b);// 群发消息for (int i = 0; i < list.size(); i++) {// System.out.println("群发");SocketThread st = list.get(i);// 向其他客户端发出画图消息st.sendDraw(x1, y1, x2, y2,c);}} else if (type == 3) {if (list.size() <= 1) {return;}// 发送题目sendTitle();} else if (type == 4) {// 群发清屏消息for (int i = 0; i < list.size(); i++) {SocketThread st = list.get(i);// 向所有客户端发出画图消息st.sendClear();}} else if (type == 5) {// 接收数据包的长度int len = dis.readInt();byte[] bytes = new byte[len];dis.readFully(bytes);// 读取客户端的输入流字符串String line = new String(bytes, "GBK");// 给画图者发送评价消息drawst.sendAccess(line);}}// 客户下线关闭当前端口socket.close();// 删除队列中的对象list.remove(this);} catch (IOException e) {e.printStackTrace();}}

/* * 读取输入流的方法 */private String readLine(InputStream input) throws IOException {// 新建一个字节队列ByteArrayOutputStream bos = new ByteArrayOutputStream();DataInputStream dis = new DataInputStream(input);dis.readInt();dis.readInt();while (true) {int n = input.read();// System.out.println(n);// 回车符if (n == '\r') {continue;}// 换行符if (n == '\n') {break;}// 把读取的字节内容先保存bos.write(n);}// 把字节队列中的数据取出来byte[] bytes = bos.toByteArray();String content = new String(bytes, "GBK");return content;}/* * 向客户端发送消息的方法 */public void sendMessage(String msg) {try {// 服务器输出流写入字节byte[] bytes = msg.getBytes();int len = bytes.length;dos.writeInt(1);dos.writeInt(len);dos.write(bytes);dos.flush();} catch (IOException e) {e.printStackTrace();}}/* * 向客户端发送画图线段消息的方法 */public void sendDraw(int x1,int y1,int x2,int y2,Color color){try {//客户端输出流写入字节dos.writeInt(2);dos.writeInt(28);dos.writeInt(x1);dos.writeInt(y1);dos.writeInt(x2);dos.writeInt(y2);int red = color.getRed();int green = color.getGreen();int blue = color.getBlue();dos.writeInt(red);dos.writeInt(green);dos.writeInt(blue);dos.flush();} catch (IOException e) {e.printStackTrace();}}

     2、客户端:创建一个客户端窗体,并初始化界面,然后启动客户端线程处理。这部分比较简单不贴代码。
       客户端线程:创建对应服务端的socket套接字,连接服务器,根据通信协议,依不同的数据包类型进行处理,run方法如下

public void run (){try{System.out.println("连接服务器......");Socket socket = new Socket("127.0.0.1",6666);System.out.println("成功!");//读取对方发送的消息InputStream input = socket.getInputStream();DataInputStream dis = new DataInputStream(input);//向对方发送的消息output = socket.getOutputStream();dos = new DataOutputStream(output);while(true){//接受数据包的类型int type=dis.readInt();if(type==1){//接收数据包的长度int len = dis.readInt();byte[] bytes= new byte[len];dis.readFully(bytes);//读取客户端的输入流字符串String line = new String(bytes,"GBK");//System.out.println(line);//從服务器接收到的消息显示到界面上l.onRecvMsg(line);}else if(type == 2){//接收画图信息int len = dis.readInt();int x1 = dis.readInt();int y1 = dis.readInt();int x2 = dis.readInt();int y2 = dis.readInt();int r = dis.readInt();int g = dis.readInt();int b = dis.readInt();Color c = new Color(r,g,b);l.onDraw(x1, y1, x2, y2,c);}else if(type==3){//从服务端接受画图题目信息int len = dis.readInt();byte[] bytes= new byte[len];dis.readFully(bytes);//读取客户端的输入流字符串String str = new String(bytes,"GBK");l.onTitle(str);}else if(type == 4){//清屏l.onclear();}else if (type == 5) {// 接收数据包的长度int len = dis.readInt();byte[] bytes = new byte[len];dis.readFully(bytes);// 读取客户端的输入流字符串String line = new String(bytes, "GBK");// 给画图者增加评价消息l.onAccess(line);}}}catch (Exception e){System.out.println("失败!");e.printStackTrace();}}
其中在接受到服务端数据之后,对客户端界面处理的方法通过客户端类实现接口MsgListener来完成,这样做能使代码的设计更合理,模块之间调用更加方便。
public interface MsgListener {//在日志上显示消息public void onRecvMsg(String str);//在画图区上画图public void onDraw(int x1,int y1,int x2,int y2,Color color);//显示题目public void onTitle(String str);//清屏public void onclear();//显示评价public void onAccess(String access);}

测试截图:

  • 大小: 173.9 KB
  • 查看图片附件
0 0