您的位置:首页 > 编程语言

【Socket编程】聊天室的构建

2016-07-07 22:59 302 查看
1.服务器与客户端一对一的交谈(初级)

/**
* server
*/
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

public static void main(String[] args) throws IOException {

ServerSocket server=new ServerSocket(7878);
Socket socket=server.accept();
DataInputStream dis=new DataInputStream(socket.getInputStream());
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());

while(true){

//服务器接收客户端消息
String msg2=dis.readUTF();
System.out.println(msg2);

//服务器发送消息
String msg="[server]:welcome";
dos.writeUTF(msg);
dos.flush();
}
}
}

/**
* client
*/
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {

Socket client=new Socket("localhost",7878);
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
DataOutputStream dos=new DataOutputStream(client.getOutputStream());
DataInputStream dis=new DataInputStream(client.getInputStream());

while(true){
//从控制台输出信息
String msg=br.readLine();

//客户端发送数据
dos.writeUTF(msg);
dos.flush();
//客户端接收数据
String msg2=dis.readUTF();
System.out.println(msg2);
}
}
}


这种一对一聊天模式存在一个问题,客户端必须先发送消息,然后等服务器回应以后再接收数据,因为客户端发送和
接收消息是处于同一线程的,输入流和输出流在同一条路径下,而客户端和服务器聊天的时候是无论它是否收到了消

息都可以发送出去,所以这里需要将输入流和输出流分离开,也就是使用多线程。

2.服务器与客户端一对一的交谈(进阶)

服务器代码不变,使用多线程将客户端输入输出流分开

/**
* 客户端发送数据
*/
public class Send implements Runnable{
private BufferedReader br;
private DataOutputStream dos;
private boolean isRunning=true;//控制线程的标识

public Send() {
br=new BufferedReader(new InputStreamReader(System.in));
}

public Send(Socket client) {
this();
try {
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}

private String getMsgFromConsole(){
try {
return br.readLine();
} catch (IOException e) {

}

return "";
}

public void send(){
String msg=getMsgFromConsole();
if(msg!=null && !msg.equals("")){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}
}

@Override
public void run() {
while(isRunning){
send();
}
}
}

/**
* 客户端接收数据
*/
public class Receive implements Runnable{
private DataInputStream dis;
private boolean isRunning=true;

public Receive() {

}

public Receive(Socket client) {
try {
dis=new DataInputStream(client.getInputStream());
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
}
}

public String receive(){
String msg=null;
try {
msg=dis.readUTF();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
}
return msg;
}

@Override
public void run() {
while(isRunning){
System.out.println(receive());
}
}
}

/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {

Socket client=new Socket("localhost",7878);
new Thread(new Send(client)).start();
new Thread(new Receive(client)).start();
}
}


/**
* 关闭流工具类
*/
public class CloseUtil {
public static void  CloseALL(Closeable... io){
for(Closeable temp:io){
try {
if(temp!=null){
temp.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}


通过这样的调整,输入输出在不同的线程,但是这里的服务器只能连接一个客户端,如何使服务器对应多个客户端,
并且每个客户端之间不会相互干扰呢?需要在服务器上使用多线程。

一个客户端对应一个线程,每个客户端又有独立的输入流和输出流

3.服务器和多个客户端一次会谈

服务端加入多线程,客户端不变

/**
* server
*/
public class Server {

private List<MyChannel> all=new ArrayList<MyChannel>();//所有客户端集合

public static void main(String[] args) throws IOException {
new Server().start();
}

public void start() throws IOException{
ServerSocket server=server = new ServerSocket(7878);
while(true){
Socket client=server.accept();
MyChannel channel=new MyChannel(client);
all.add(channel);
new Thread(channel).start();
}
}

/**
* 客户端管道、线程
*/
private class MyChannel implements Runnable{

private DataInputStream dis;
private DataOutputStream dos;
private boolean isRunning=true;

public MyChannel() {

}

public MyChannel(Socket client) {
try {
dis=new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis,dos);
all.remove(this);//移除自身
}
}

public String receive(){
try {
return dis.readUTF();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
all.remove(this);
}
return "";
}

/**
* 给其他客户端发送消息
*/
private void sendOthers(){
String msg=receive();

for(MyChannel temp:all){
if(temp==this){
continue;
}
temp.send(msg);
}
}

public void send(String msg){
if(msg==null || msg.equals("")){
return ;
}

try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos);
all.remove(this);
}
}

@Override
public void run() {
sendOthers();
}
}
}

需要注意的是,经过这样的改变,服务端只能为每个客户端转发一次消息,进行一次会谈,也就是假如我有一个
Client-A,Client-B,Client-C,A给BC只能发一次消息,同样的B,C也只能给AC,AB发一次消息,这是因为在start方法

中,server.accept()在死循环中,也就是除非有新的客户端连接到服务器,服务器的main线程会一直处于阻塞状态,

如果将这句代码放到死循环外面,会报错

4.客户端私聊一次会谈

为每个客户端加用户名,在进入聊天室时做提示,比如“xxx进入了聊天室”,“欢迎进入聊天室”

假设这里有两个客户端,depp,tom,depp要和tom私聊时,直接在控制台输入:@tom:XXX

这样消息就只能发送给tom

【客户端 receive类代码不变】

/**
* server
*/
public class Server {

private List<MyChannel> all=new ArrayList<MyChannel>();//所有客户端集合

public static void main(String[] args) throws IOException {
new Server().start();
}

public void start() throws IOException{
ServerSocket server=server = new ServerSocket(7878);
while(true){
Socket client=server.accept();
MyChannel channel=new MyChannel(client);
all.add(channel);
new Thread(channel).start();
}
}

/**
* 客户端管道、线程
*/
private class MyChannel implements Runnable{

private DataInputStream dis;
private DataOutputStream dos;
private boolean isRunning=true;
private String username;

public MyChannel() {

}

public MyChannel(Socket client) {
try {
dis=new DataInputStream(client.getInputStream());
dos=new DataOutputStream(client.getOutputStream());
this.username=receive();
this.send("welcome to chat room !");
this.sendOthers("进入了聊天室!");

} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis,dos);
all.remove(this);//移除自身
}
}

public String receive(){
try {
return dis.readUTF();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dis);
all.remove(this);
}
return "";
}

/**
* 给其他客户端发送消息
*/
private void sendOthers(String msg){
//判断是否为私聊
if(msg.startsWith("@") && msg.indexOf(":")>-1){

//获取名字
String name=msg.substring(1, msg.indexOf(":"));

//获取内容
String content=msg.substring(msg.indexOf(":")+1);

for(MyChannel temp:all){
if(temp.username.equals(name)){
temp.send(this.username+" whisper : "+content);
}
}
}else{
for(MyChannel temp:all){
if(temp==this){
continue;
}
temp.send(this.username+" "+msg);
}
}
}

public void send(String msg){
if(msg==null || msg.equals("")){
return ;
}

try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos);
all.remove(this);
}
}

@Override
public void run() {
sendOthers(receive());
}
}
}

/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {

System.out.println("please input username:");
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String username=br.readLine();
if(username.equals("")){
return ;
}
Socket client=new Socket("localhost",7878);
new Thread(new Send(client,username)).start();
new Thread(new Receive(client)).start();
}
}

/**
* 客户端发送数据
*/
public class Send implements Runnable{
private BufferedReader br;
private DataOutputStream dos;
private String username;
private boolean isRunning=true;//控制线程的标识

public Send() {
br=new BufferedReader(new InputStreamReader(System.in));
}

public Send(Socket client,String username) {
this();
try {
dos=new DataOutputStream(client.getOutputStream());
this.username=username;
send(this.username);
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}

private String getMsgFromConsole(){
try {
return br.readLine();
} catch (IOException e) {

}

return "";
}

public void send(String msg){
if(msg!=null && !msg.equals("")){
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
isRunning=false;
CloseUtil.CloseALL(dos,br);
}
}
}

@Override
public void run() {
while(isRunning){
send(getMsgFromConsole());
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  socket thread java j2se