社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
近来感觉秋招无望,学习Socket的时候,便做了个基于Socket的群聊工具;
先看看最终效果吧
socket又称为“套接字”,建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。 ——来自百度百科
socket通信步骤(TCP)
1.建立SocketServer(服务端) 和 Socket (客户端)
2.打开两个端之间的输入输出流
3.进行读写操作
4.关闭socket与流
先来看第一步
SocketServer 与 Socket都是 java.net包里的,进行TCP通信的时候需要建立客户端与服务端。SocketServer 通过绑定端口(Port)来实现监听,而Socket则是指定服务端端口(Port)与地址(IP)。
下面来看一个简单的通信例子
//Server部分
ServerSocket server = new ServerSocket(6063);//实例化(传入端口号)
Socket s = server.accept();//调用accept接收socket
BufferedReader in =
new BufferedReader(new InputStreamReader(s.getInputStream()));//获得socket的输入流(同样的,可以通过getOutput
Stream来获取输出流)
while((msg= in.readLine())!=null){
System.out.println(msg);
}
in.close();
s.close();
//Client
Socket s = new Socket("192.168.1.133",6063);//实例化Socket传入指定服务端地址和端口号
System.out.println("客户端启动...");
BufferedReader re = new BufferedReader(new InputStreamReader(System.in));//用输入流读取键盘的输入
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);//获取socket的输出流,第二个参数表示会自动flush
String msg2;
while(true){
msg2 = re.readLine();
pw.println(msg2);//输出(自动flush)
}
这样我们就简单了实现了socket通信
当然要实现即时聊天并非这么几行就能搞定的。
即时聊天,重点在于流的控制,需要开辟多条线程去分别做不同的事情。
下面来本次项目的PC服务端代码
public class Server implements Runnable {
List<Socket> sockets = new ArrayList<>();
private static final String ServerIp = "192.168.1.133";
private static final int ServerPort = 6066;
@Override
public void run() {
try{
System.out.println("服务端启动...");
ServerSocket server = new ServerSocket(ServerPort);
while(true){
Socket client = server.accept();
sockets.add(client);
receiveThread re = new receiveThread(client);
re.start();
}
}catch(Exception e){
System.out.println("------S:Error 1-------");
e.printStackTrace();
}
}
//接受msg线程
public class receiveThread extends Thread{
Socket socket;
private BufferedReader br;
private PrintWriter pw;
public String msg;
public receiveThread(Socket s){
socket = s;
}
public void run(){
try{
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));//不转码在Android端会乱码
msg = "sys:##:"+"欢迎"+socket.getInetAddress()+"进入聊天室,当前人数为"+sockets.size();
sendMsg(InetAddress.getByName("1"));
while((msg = br.readLine())!=null){
if(msg.equals("EndEndClosethesocket")){
close(socket.getInetAddress());
}else{
msg = socket.getInetAddress()+":##:"+msg;
sendMsg(socket.getInetAddress());
}
}
}catch(Exception e){
e.printStackTrace();
}
}
public void sendMsg(InetAddress ip){
try{
System.out.println(msg);
for(int i = 0;i < sockets.size();i++){
if(!ip.equals(sockets.get(i).getInetAddress())){
pw = new PrintWriter(new OutputStreamWriter(sockets.get(i).getOutputStream(),"UTF-8"),true);
pw.println(msg);
pw.flush();
}
}
}catch(Exception e){
e.printStackTrace();
}
}
public void close(InetAddress ip){
for(int i = 0;i < sockets.size();i++){
if(sockets.get(i).getInetAddress()==ip){
sockets.remove(i);
msg ="sys:##:"+ip+"已经离开了聊天室";
try{
sendMsg(InetAddress.getByName("1"));
}catch(Exception e){
e.printStackTrace();
}
break;
}
}
}
}
public static void main(String args[]){
Thread thread = new Thread(new Server());
thread.start();
}
}
可以看到,这里用了一个ArrayList来存储SocketServer接收的Socket
然后将接收的Socket作为参数传入自定义的线程receiveThread中,然
后在这个线程中循环读取Client端发来的消息。然后通过sendMsg方法
广播这条消息。
下面看看Android客户端的主要代码
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...//详细代码请移步GitHub
if(socket==null||socket.isClosed()||!socket.isConnected()){
getIPandPort();
creatSock();
Log.d("aaaaaaa", "onCreate: 1111111111");
}else {
Log.d("aaaaaaa", "onCreate: 122222222222");
heart();
}
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//发送button监听
if(sendMsg(edit.getText().toString())){
edit.setText("");
}
}
});
}
//读取ip与Port
public void getIPandPort(){
SharedPreferences preferences = getSharedPreferences("data", Context.MODE_PRIVATE);
ServerIp = preferences.getString("ip","192.168.1.133");
ServerPort = preferences.getInt("port",6066);
title.setText(ServerIp+"n"+ServerPort);
}
//心跳检测
public void heart(){
if (socket!=null){
try{
socket.sendUrgentData(0xff);
}catch (Exception e){
e.printStackTrace();
reconn();
}
}else {
reconn();
}
}
//建立连接
public void creatSock(){
new Thread(new Runnable() {
@Override
public void run() {
try{
// if (socket!=null){
// clconn();
// }
InetAddress inetAddress = InetAddress.getByName(ServerIp);
socket = new Socket(inetAddress, ServerPort);
AcceptMsg();
}catch (Exception e) {
e.printStackTrace();
reconn();
Log.d("aaaa", "run: 连接失败");
}
}
}).start();
}
public void reconn(){
sendMessenger(new Msg("无法连接服务器...请重设PORT或IP",2,"sys"));
showReconn();
}
public void sendMessenger(final Msg msg){
runOnUiThread(new Runnable() {
@Override
public void run() {
msgs.add(msg);
adapter.notifyDataSetChanged();
}
});
}
//发送
private boolean sendMsg(final String msg){
if(socket!=null&&socket.isConnected()){
if (!msg.equals("")){
new Thread(new Runnable() {
@Override
public void run() {
try{
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
pw.println(msg);
if (!msg.equals("EndEndClosethesocket")){
sendMessenger(new Msg(msg,0,"Me"));
}
Log.d("XXXXXXX", "发送成功");
}catch (Exception e) {
heart();
}
}
}).start();
return true;
}
}
return false;
}
//接受
public void AcceptMsg(){
if (socket.isConnected()&&!socket.isClosed()){
new Thread(new Runnable() {
@Override
public void run() {
try{
re = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while((socketmsg=re.readLine())!=null){
String[] msg = socketmsg.split(":##:");
Msg m;
if(msg[0].equals("sys")){
m = new Msg(msg[1],2,msg[0]);
}else{
Log.d("sxxxxx", "run: "+msg[0]);
String ip = msg[0].substring(msg[0].length()-3,msg[0].length());
m = new Msg(msg[1],1,ip);
}
sendMessenger(m);
Log.d("xxxxxxxxx", "接受成功"+socketmsg);
}
}catch (Exception e){
e.printStackTrace();
heart();
Log.d("aaaa", "run: 接受失败");
}
}
}).start();
}
heart();
}
在安卓端实例化Socket时要注意将IP地址转化为InetAddress,再将参数传入。
另外,Socket操作不能在主线程里直接操作,否则会报错,应该新建线程对其
进行操作(无论是建立连接,发送消息,还是接收消息);
本次项目开发也遇到了许多坑。
一开始想用ViewPage+fragement来实现更好的界面效果,由于fragment的
重载的大坑,导致我最终放弃直接用fragment实现聊天界面
还有就是心跳包的问题,重复发送多次会导致服务端崩溃,捣鼓了一下午也没
弄好,最后还是没用上。
这次就到这里咯,收拾收拾好心情,面对十月的秋招吧!!!!
——-来自offer颗粒无收的大四狗
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!