Java通信之客户端的创建以及客户端和服务器的简单交互
一、客户端的创建
现在所使用的是windows自带telnet客户端,并且只能发送英文,今天我们将自己创建客户端,并让客户端和服务器进行简答的交互,跟前面的压缩和解压缩一样,客户端和服务器不是一个工程,是两个工程;分开写
客户端创建的实现步骤:
先创建Socket对象,并给定ip和端口就可以完成和服务器的连接
Socket client = new Socket(ip地址,端口号);
二、同步消息和异步消息(与客户端和服务器的交互有关)
同步消息:消息发送方发送完消息后,一定要等对方反馈消息后,才能有后续消息发送动作(登录,注册是否成功)
异步消息:消息发送发只管发消息,不需要的到对方的反馈(消息的发送和接收)
三、客户端和服务器的简单交互
拿我们前面简单的多人聊天室来说,首先,客户端的连接服务器;一旦连接成功,服务器会发送消息给客户端,所连接成功,并要求客户端输入用户名和密码进行登录;当输入用户名和密码以后,服务器端会进行校验,返回给客户端一个信息,看是否登录成功;如果登录成功,则可以多人聊天了,否则登录失败,要求重新登录;前面这一部分登录都是属于同步消息,一定要等到对方反馈消息后,才能有后续等到消息发送动作,一旦登录成功,后面服务器需要一直接受来自客户端的消息,并把消息转发出去,而客户端则需要一直处于接受消息的状态,发送消息和接收消息是一步消息,不需要等待反馈消息;
四、源代码
服务器端代码:
package com.huaxin.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class Myserver {
public static ArrayList<ServerThread>list =new ArrayList<ServerThread>();
public void initServer() {
try {
//创建服务器对象,并指定端口号
ServerSocket server = new ServerSocket(9090);
System.out.println("服务器已经建立......");
//不断获取客户端的连接
while(true){
Socket socket =server.accept();
System.out.println("客户端连接进来了......");
//当有客户端连接进来以后,开启一个线程,用来处理该客户端的逻辑,
ServerThread st = new ServerThread(socket);
st.start();
//添加该客户端到容器中
list.add(st);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.huaxin.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ServerThread extends Thread {
public Socket socket;
public InputStream ins;
public OutputStream ous;
public ServerThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
// 获取输入输出流
ins = socket.getInputStream();
ous = socket.getOutputStream();
// 发送消息给客户端
String msg = "welcome to zhou's server !";
sendMsg(ous, msg);
// 发送要求登录信息给客户端
String userinfo = "please input your name:";
sendMsg(ous, userinfo);
// 获取客户端输入的用户名
String userName = readMsg(ins);
// 发送要求密码信息给客户端
String pwd = "please input your password:";
sendMsg(ous, pwd);
// 获取客户端输入的密码
String pass = readMsg(ins);
// 登录验证
boolean falg = loginCheck(userName, pass);
// 校验不通过时,循环校验
while (!falg) {
msg="no";
sendMsg(ous, msg);
msg = "Fail to connect server......";
sendMsg(ous, msg);
msg = "please check your name and password and login again.....";
sendMsg(ous, msg);
msg = "please input your name:";
sendMsg(ous, msg);
// 获取客户端输入的用户名
userName = readMsg(ins);
// 发送要求密码信息给客户端
msg = "please input your password:";
sendMsg(ous, msg);
// 获取客户端输入的密码
pass = readMsg(ins);
falg = loginCheck(userName, pass);
}
//发送登录成功的结果给客户端
msg="ok";
sendMsg(ous, msg);
// 校验成功后:开始聊天
msg = "successful connected..... you can chat with your friends now ......";
sendMsg(ous, msg);
// 聊天处理逻辑
//读取客户端发来的消息
msg=readMsg(ins);
System.out.println("客户端已经接到消息:"+msg);
//输入bye结束聊天
while(!"bye".equals(msg)){
//给容器中的每个对象转发消息
for (int i = 0; i <Myserver.list.size(); i++) {
ServerThread st =Myserver.list.get(i);
//不该自己转发消息
if(st!=this){
System.out.println("转发消息......");
sendMsg(st.ous, userName+" is say:"+msg);
System.out.println("转发消息成功......");
}
}
//等待读取下一次的消息
msg=readMsg(ins);
}
} catch (Exception e) {
System.out.println("客户端不正常关闭......");
//e.printStackTrace();
}
//有异常后统一将流关闭
try {
ins.close();
ous.close();
socket.close();
//将当前已经关闭的客户端从容器中移除
Myserver.list.remove(this);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 校验客户端输入的账号和密码的函数,由于没有数据库,暂时写死了
public boolean loginCheck(String name, String pwd) {
if (name.equals("zhou") && pwd.equals("zhou") || name.equals("user") && pwd.equals("pwd")
|| name.equals("huaxinjiaoyu") && pwd.equals("huaxinjiaoyu")) {
return true;
}
return false;
}
// 发送消息的函数
public void sendMsg(OutputStream os, String s) throws IOException {
// 向客户端输出信息
byte[] bytes = s.getBytes();
os.write(bytes);
os.write(13);
os.write(10);
os.flush();
}
// 读取客户端输入数据的函数
public String readMsg(InputStream ins) throws Exception {
// 读取客户端的信息
int value = ins.read();
// 读取整行 读取到回车(13)换行(10)时停止读
String str = "";
while (value != 10) {
// 点击关闭客户端时会返回-1值
if (value == -1) {
throw new Exception();
}
str = str + ((char) value);
value = ins.read();
}
str = str.trim();
return str;
}
}
服务器端启动类:
package com.huaxin.server;
public class Test {
public static void main(String[] args) {
Myserver ms = new Myserver();
ms.initServer();
}
}
客户端代码:
package com.huaxin.client;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Myclient {
public static void main(String[] args) {
Myclient mc = new Myclient();
mc.initClient();
}
public void initClient() {
try {
// 创建客户端对象
Socket client = new Socket("localhost", 9090);
// 获取客户端的输入输出流
final InputStream ins = client.getInputStream();
final OutputStream ous = client.getOutputStream();
// 先接收服务器发送的欢迎词
String msg = readMsg(ins);
System.out.println(msg);
// 接收服务端发送过来输入用户名的请求
String requestName = readMsg(ins);
System.out.println(requestName);
// 获取用户名信息,从控制台
final Scanner scanner = new Scanner(System.in);
String username = scanner.nextLine();
// 发送用户名
sendMsg(ous, username + "");
// 读取密码请求
String requestPwd = readMsg(ins);
System.out.println(requestPwd);
// 从控制台扫描密码
String pwd = scanner.nextLine();
// 把密码发送给服务器
sendMsg(ous, pwd + "");
// 获取验证结果
String result = readMsg(ins);
//如果登录失败,则接受服务器端发过来的提示消息
while(!result.equals("ok")){
//接收"Fail to connect server......"
String message=readMsg(ins);
System.out.println(message);
//接收"please check your name and password and login again....."
message=readMsg(ins);
System.out.println(message);
//接收 "please input your name:""
message=readMsg(ins);
System.out.println(message);
//重新发送用户名给服务器
username = scanner.nextLine();
// 发送用户名
sendMsg(ous, username + "");
//接受密码请求"please input your password:"
message=readMsg(ins);
System.out.println(message);
//发送密码给服务器
pwd = scanner.nextLine();
// 发送用户名
sendMsg(ous, pwd + "");
//接收服务器返的信息
result = readMsg(ins);
}
//如果登录成功,则可以开始聊天了
if (result.equals("ok")) {
//System.out.println("登陆成功");
// 发送消息线程
new Thread() {
public void run() {
try {
while (true) {
// 从控制台扫描一行数据
String message = scanner.nextLine();
sendMsg(ous, message + "");
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
//读取消息线程
new Thread() {
public void run() {
try {
while (true) {
String message = readMsg(ins);
System.out.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
} else {
System.out.println("登陆失败");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String readMsg(InputStream ins) throws Exception {
int value = ins.read();
String str = "";
while (value != 10) {
// 代表客户单不正常关闭
if (value == -1) {
throw new Exception();
}
str = str + (char) value;
value = ins.read();
}
str = str.trim();
return str;
}
// 发送消息的函数
public void sendMsg(OutputStream ous, String str) throws Exception {
byte[] bytes = str.getBytes();
ous.write(bytes);
ous.flush();
}
}
五、运行结果截图:
先开启服务器:
错误登录测试:
重新开启服务器,并开启三个客户端(启动三次客户端的代码,输入不同的用户名和密码即可)
客户端1、
客户端2
客户端3
六、总结
1、客户端的创建其实还是挺简单的,就两行代码
2、客户端和服务器端的交互看似很复杂,其实,如果服务器那端早已经在前面写好了,客户端这边只要只要根据服务器那边来写代码就好了;需要注意的是客户端和服务器的读消息和发送消息的两个函数功能不要写错了;如果这里没问题,我们只需要看服务器那边怎么写的代码,客户单照着写就好了,比如,但客户端连接服务器后,服务器那边首先会发送一条欢迎的消息给客户端,所以,客户端建立好并连接服务器以后,那么,第一步就是接收服务器发送过来的欢迎词;接下来第二步,服务器会发送用户名请求给客户端,那么客户端首先要做的是接收这条消息,然后输入用户名,并将输入的用户名发送给服务器,这里用到了Scanner类,不懂的请自行百度!然后,就是这么一步一步按部就班的来;服务器那边怎么写,客户端这边就怎么接,就这么简单!
3、经验分享:遇到问题,不要慌;出错真的是好事,以前会害怕出错,现在真的是一点都不害怕了;调试bug的能力才是衡量一个程序员高低好坏的标准,所以,遇到错误不要怕!要勇于直面bug;最常用的一招抓bug的方法就是打印一条语句,看这条语句是否执行了,没有执行,说明这里没有执行到,在我的程序中,会看到很多被注释的system.out.println语句,那就是调试bug用的!
共勉!