1.如何创建客户端和服务端,客户端如何连接服务端
创建服务端:
ServerSocket serverSocket = new ServerSocket(8080);
1.1 创建客户端
/*构造器参数1:服务端IP地址
:服务端端口号
*/
Socket socket = new Socket("127.0.0.1",8080);
System.out.println("客户端创建完成....");
1.2 服务端获取客户端连接
System.out.println("等待客户端连接....");
Socket accept = serverSocket.accept();//会让运行的线程进入阻塞状态
System.out.println("出现客户端连接...");
异常:
1.1 端口被占用异常:
java.net.BindException: Address already in use: JVM_Bind
2 服务端获取客户端连接的方式
1.1 单次获取客户端连接
Socket accept = serverSocket.accept();
1.2 使用死循环嵌套,让服务器一直监听获取客户端连接
while(true){
Socket accept = serverSocket.accept();//会让运行的线程进入阻塞状态
}
1.3 在获取连接后保存到List集合中,方便以后发送消息使用
/**
* 以后会出现多个线程分别写入和读取操作(线程不安全问题)
*/
List<Socket> clientList = Collections.synchronizedList(new ArrayList<>());
1.4 服务端有多个线程要做不同的事情,监听客户端连接放到新线程
//启动线程1,监听客户端的连接
new Thread(){
@Override
public void run() {
//获取客户端连接
while(true){
System.out.println("等待客户端连接....");
Socket accept = null;//会让运行的线程进入阻塞状态
try {
accept = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
clientList.add(accept);
System.out.println("出现客户端连接...已有【"+clientList.size()+"】个连接...");
}
}
}.start();
3.服务端监听客户端断开
原理:服务端循环调用保存的客户端Socket对象去发送消息如果发送消息失败说明就断开连接
//2.线程2:监听客户端断开连接的线程
new Thread(){
@Override
public void run() {
System.out.println("服务端状态心跳监听开启...");
while(true){
for(int i=0;i<clientList.size();i++){
Socket socket = clientList.get(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
OutputStream os = null;
try {
socket.sendUrgentData(0xFF);//使用紧急数据发送方法心跳测试客户端你是否存在(让线程沉睡500毫秒)
// os = socket.getOutputStream();
// os.write("@##@".getBytes());//发送心跳数据
} catch (IOException e) {//发送失败客户端断开连接
//e.printStackTrace();
clientList.remove(socket);
System.out.println("客户端断开连接...还剩【"+clientList.size()+"】个连接");
}
}
}
}
}.start();
4.服务端监听客户端发送的消息
//3.线程3:监听客户发送给服务端的消息
new Thread(){
@Override
public void run() {
System.out.println("服务端消息监听开启...");
while(true){
for(int i=0;i<clientList.size();i++){
Socket socket = clientList.get(i);
try {
InputStream is = socket.getInputStream();
int available = is.available();
if(available>0){//正常接收到客户端消息
byte [] arr = new byte[available];
is.read(arr);
String str = new String(arr);
System.out.println("客户端说:"+str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}.start();
5.客户端与服务端发送消息,内容分类
(1)告诉服务器我上线了(告诉服务器的QQ是多少)
信息模板: GOON:QQ号码
目的:
将Socket对象存放到Map<String,Socket>集合中,key:QQ号,value:客户端Socket对象
/**
* 存放key:QQ号码
* value:Socket对象
* 的map集合
*/
Map<String, Socket> socketMap = Collections.synchronizedMap(new HashMap<>());
同时在断开连接的时候要添加一个判断去移除map集合元素
//e.printStackTrace();
clientList.remove(socket);
String qqNumber = getKeyByValue(socketMap, socket);
if(qqNumber!=null){
socketMap.remove(qqNumber);
}
System.out.println("客户端断开连接...还剩【"+clientList.size()+"】个连接");
socketMap根据值获取QQ号码的方法
public static synchronized String getKeyByValue(Map<String,Socket> map,Socket socket){
Set<String> keys = map.keySet();
for(String key:keys){
Socket socket1 = map.get(key);
if(socket1.equals(socket)){
return key;
}
}
return null;
}
(2)告诉服务器我要发消息给谁?
信息模板:SEND:对方的QQ号码:要发送的信息
在之前的基础上改写“服务器接收客户端消息代码”
//3.线程3:监听客户发送给服务端的消息
new Thread(){
@Override
public void run() {
System.out.println("服务端消息监听开启...");
while(true){
for(int i=0;i<clientList.size();i++){
Socket socket = clientList.get(i);
try {
InputStream is = socket.getInputStream();
int available = is.available();
if(available>0){//正常接收到客户端消息
byte [] arr = new byte[available];
is.read(arr);
String str = new String(arr);
String type = str.substring(0, 4);
//信息模板: GOON:QQ号码
if("GOON".equals(type)){//上线消息
int i1 = str.indexOf(":");
String qqStr = str.substring(i1, str.length());
socketMap.put(qqStr,socket);
}else if("SEND".equals(type)){//发送消息
//SEND:对方的QQ号码:要发送的信息
//将SEND:去掉
String newStr = str.substring(5, str.length());
String msg = newStr.substring(newStr.indexOf(":")+1, str.length());
String sendQQ = newStr.substring(0, newStr.indexOf(":"));
System.out.println("转发信息:TO>"+sendQQ+"\n消息:"+sendQQ);
//找出对应QQ号码的Socket对象,进行消息的转发
Socket sendSocket = socketMap.get(sendQQ);
OutputStream sendOs = sendSocket.getOutputStream();
sendOs.write(msg.getBytes());
sendOs.flush();
System.out.println("转发成功!");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}.start();
6.客户端代码编写
1.客户端监听服务转发的消息
new Thread(){
@Override
public void run() {
System.out.println("客户端消息监听开启...");
while(true){
try {
InputStream is = socket.getInputStream();
int available = is.available();
if(available>0){
byte [] arr = new byte[available];
is.read(arr);
String str = new String(arr);
System.out.println("---服务端转发:--------------------\n");
System.out.println("---"+str+"\n----------------------------");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
2.客户端要编写一个发送信息的方法
封装发送消息的方法:
/**
* 发送消息给服务端
* @param socket 发送消息的客户端对象
* @param msg 信息内容
*/
public static synchronized void sendMsg(Socket socket,String msg){
try{
OutputStream os = socket.getOutputStream();
os.write(msg.getBytes());
os.flush();
}catch (IOException e){
e.printStackTrace();
}
}
建立一个线程去可以让客户端一直发送消息
new Thread(){
Scanner sc = new Scanner(System.in);
@Override
public void run() {
System.out.println("客户端发送消息功能加载完成....");
while(true){
System.out.println("请输入发送的QQ号码:");
String qq = sc.next();
System.out.println("请输入发送的消息:");
String next = sc.next();
String msg = "SEND:"+qq+":"+next;
sendMsg(socket,msg);
}
}
}.start();
客户端要定义一个QQ号码:并且告诉服务器上线
//发送一个消息给服务端告诉我以上线,附上QQ号
String QQNumber = UUID.randomUUID().toString().replaceAll("-","").substring(0,6).toUpperCase();
//封装信息体 告诉上线: GOON:QQ
String goonMsg = "GOON:"+QQNumber;
sendMsg(socket,goonMsg);
System.out.println("我的QQ号码:"+QQNumber);