基于socket的聊天程序,socket 教程
如何使用Socket.IO编写聊天应用程序
我们将首先通过查看客户端代码。所有聊天互动都有HomeView处理,首先需要在 /public/js/models/main.js中定义HomeModel。
var HomeModel = Backbone.Model.extend({
?defaults: {
? ?// Backbone collection for users
? ?onlineUsers: new UserCollection(),
? ?// Backbone collection for user chats, 初始化一个预定义聊天模型
? ?userChats: new ChatCollection([
? ? ?new ChatModel({sender: '', message: 'Chat Server v.1'})
? ?])
?},
?// 添加一个新用户到 onlineUsers collection
?addUser: function(username) {
? ?this.get('onlineUsers').add(new UserModel({name: username}));
?},
?// 从onlineUsers collection中移除一个用户
?removeUser: function(username) {
? ?var onlineUsers = this.get('onlineUsers');
? ?var u = onlineUsers.find(function(item) {
? ? ? ? ?return item.get('name') == username;
? ? ? ?});
? ?if (u) {
? ? ?onlineUsers.remove(u);
? ?}
?},
?// 添加一个新的聊天到 userChats collection
?addChat: function(chat) {
? ?this.get('userChats').add(new ChatModel({sender: chat.sender, message: chat.message}));
?},
});
我们利用Backbone集合来侦听集合变化。这些集合的更新会直接由视图自动反映出来。接下来,需要在/public/index.html中定义home模板。
script type="text/template" id="home-template"
?div class="row"
? ?div class="col-md-10"
? ? ?div class="panel panel-default"
? ? ? ?div class="panel-heading"Lobby/div
? ? ? ?div class="panel-body"
? ? ? ? ?div class="nano"
? ? ? ? ? ?div class="content"
? ? ? ? ? ? ?div class="list-group" id="chatList"/div
? ? ? ? ? ?/div
? ? ? ? ?/div
? ? ? ? ?form
? ? ? ? ? ?input class="form-control" type="text" id="chatInput"/input
? ? ? ? ?/form
? ? ? ?/div
? ? ?/div
? ?/div
? ?div class="col-md-2"
? ? ?div class="panel panel-default"
? ? ? ?div class="panel-heading"
? ? ? ? ?h3 class="panel-title"Online Users span class="badge pull-right" id="userCount"/span/h3
? ? ? ?/div
? ? ? ?div class="panel-body"
? ? ? ? ?div class="nano"
? ? ? ? ? ?div class="content"
? ? ? ? ? ? ?div class="list-group" id="userList"/div
? ? ? ? ? ?/div
? ? ? ? ?/div
? ? ? ?/div
? ? ?/div
? ?/div
?/div
/script
聊天客户端
接下来,?让我们来定义?我们的?Socket.IO?聊天客户端?。?客户端?与服务器端的通信,主要通过发送消息和监听通知?来完成。?这些通知?触发事件?与所述控制器?进行通信。请参阅?下面?/public/?JS?/?socketclient.js的代码?。
var ChatClient = function(options) {
?// 避免冲突
?var self = this;
?// app event bus
?self.vent = options.vent;
?// server hostname replace with your server's hostname eg:
?self.hostname = '';
?// connects to the server
?self.connect = function() {
? ?// connect to the host
? ?self.socket = io.connect(self.hostname);
? ?// set responseListeners on the socket
? ?self.setResponseListeners(self.socket);
?}
?// send login message
?self.login = function(name) {
? ?self.socket.emit('login', name);
?}
?// send chat message
?self.chat = function(chat) {
? ?self.socket.emit('chat', chat);
?}
?self.setResponseListeners = function(socket) {
? ?// handle messages from the server
? ?socket.on('welcome', function(data) {
? ? ?// request server info
? ? ?socket.emit('onlineUsers');
? ? ?self.vent.trigger('loginDone', data);
? ?});
? ?socket.on('loginNameExists', function(data) {
? ? ?self.vent.trigger('loginNameExists', data);
? ?});
? ?socket.on('loginNameBad', function(data) {
? ? ?self.vent.trigger('loginNameBad', data);
? ?});
? ?socket.on('onlineUsers', function(data) {
? ? ?console.log(data);
? ? ?self.vent.trigger('usersInfo', data);
? ?});
? ?socket.on('userJoined', function(data) {
? ? ?self.vent.trigger('userJoined', data);
? ?});
? ?socket.on('userLeft', function(data) {
? ? ?self.vent.trigger('userLeft', data);
? ?});
? ?socket.on('chat', function(data) {
? ? ?self.vent.trigger('chatReceived', data);
? ?});
?}
}
使用Socket.IO可以非常简单的发送和接受通信数据,上面的代码中,使用了下面的两个方法
socket.emit(message, [callback])??向服务器端发送消息
socket.on(message, callback)????? 用于接收来自服务器的消息
让我们来看一下他们的通信协议
主控制器
客户端最后一步,主控制器,它控制了VIEW,MODEL和socket客户端,代码在/public/js/main.js中
var MainController = function() {
?var self = this;
?// Event Bus for socket client
?self.appEventBus = _.extend({}, Backbone.Events);
?// Event Bus for Backbone Views
?self.viewEventBus = _.extend({}, Backbone.Events);
?// initialize function
?self.init = function() {
? ?// create a chat client and connect
? ?self.chatClient = new ChatClient({vent: self.appEventBus});
? ?self.chatClient.connect();
? ?// create our views, place login view inside container first.
? ?self.loginModel = new LoginModel();
? ?self.containerModel = new ContainerModel({
? ? ?viewState: new LoginView({
? ? ? ?vent: self.viewEventBus,
? ? ? ?model: self.loginModel
? ? ?})
? ?});
? ?self.containerView = new ContainerView({model: self.containerModel});
? ?self.containerView.render();
?};
?// View Event Bus Message Handlers
?self.viewEventBus.on('login', function(name) {
? ?// socketio login
? ?self.chatClient.login(name);
?});
?self.viewEventBus.on('chat', function(chat) {
? ?// socketio chat
? ?self.chatClient.chat(chat);
?});
?// Socket Client Event Bus Message Handlers
?// triggered when login success
?self.appEventBus.on('loginDone', function() {
? ?self.homeModel = new HomeModel();
? ?self.homeView ?= new HomeView({vent: self.viewEventBus, model: self.homeModel});
? ?// set viewstate to homeview
? ?self.containerModel.set('viewState', self.homeView);
?});
?// triggered when login error due to bad name
?self.appEventBus.on('loginNameBad', function(name) {
? ?self.loginModel.set('error', 'Invalid Name');
?});
?// triggered when login error due to already existing name
?self.appEventBus.on('loginNameExists', function(name) {
? ?self.loginModel.set('error', 'Name already exists');
?});
?// triggered when client requests users info
?// responds with an array of online users.
?self.appEventBus.on('usersInfo', function(data) {
? ?var onlineUsers = self.homeModel.get('onlineUsers');
? ?var users = _.map(data, function(item) {
? ? ?return new UserModel({name: item});
? ?});
? ?onlineUsers.reset(users);
?});
?// triggered when a client joins the server
?self.appEventBus.on('userJoined', function(username) {
? ?self.homeModel.addUser(username);
? ?self.homeModel.addChat({sender: '', message: username + ' joined room.'});
?});
?// triggered when a client leaves the server
?self.appEventBus.on('userLeft', function(username) {
? ?self.homeModel.removeUser(username);
? ?self.homeModel.addChat({sender: '', message: username + ' left room.'});
?});
?// triggered when chat receieved
?self.appEventBus.on('chatReceived', function(chat) {
? ?self.homeModel.addChat(chat);
?});
}
最后,我们需要定义一个MainController入口,调用init方法,代码位于/public/js/main.js中
聊天服务器端
应用程序的最后一部分是聊天服务器。它主要负责维护在线用户列表,广播聊天消息。比如,首先,服务器会给一个新的客户的连接请求命名,然后通过刚刚建立的socket,连接事件handlers。socket handler处理如下事件:??? socket.on(message, callback)? - 在收到新邮件时回调函数被调用。消息可以是任何类型的数据,这取决于发送的消息。??? socket.on('disconnect', callback) - 当socket断开连接时候,回调函数被调用。??? socket.emit(message, args) - 通过socket发送消息。???? socket.broadcast.send(message, args)? - 广播信息到除发送者之外的所有socket。现在,我们已经看到了handler socket是如何工作的。首先,需要在/scripts/chatserver.js中定义一个用户模型 :
总结
我们已经看到了如何使用Backbone和Socket.IO构建一个简单的聊天应用程序。还有很多没有在本文中涉及的Socket.IO性能,例如rooms和namespaces。通过这篇文章,我们可以看出使用Socket.IO很容易的在客户端和服务器端交换消息。
设计一个基于Socket结构的简易聊天程序。
package chat1;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
public class Client extends JFrame{
/**
*
*/
private static final long serialVersionUID = -4733717749265129757L;
Container con=null;
JTextArea jta = null;
JTextField jtf = null;
//ArrayList al = new ArrayList();
//ServerSocket ss = null;
Socket s = null;
Client(String ip,int port){
try{
s=new Socket(ip,port); //创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
launchFrame();
}
catch(Exception e){
e.printStackTrace();
}
new Thread(new ClientR()).start();
}
public void sent(String str){ //发送消息方法
try{
DataOutputStream dos = new DataOutputStream(s.getOutputStream()); // 创建一个新的数据输出流,将数据写入指定 返回s的套接字的输出流。
dos.writeUTF(str);
}
catch(Exception e){
e.printStackTrace();
}
}
public void disconnect() throws Exception
{
s.close(); //失去连接,关闭线程s
}
class ClientR implements Runnable{//客户端运行
/*Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread 类实现了 Runnable。
激活的意思是说某个线程已启动并且尚未停止。
此外,Runnable 为非 Thread 子类的类提供了一种激活方式。
通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。
大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。这很重要,
因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
*/
public void run(){
try{
DataInputStream dis = new DataInputStream(s.getInputStream());//使用指定的底层 s.getInputStream(s的套接字的输入流) 创建一个 DataInputStream(数据输入流)
String str = dis.readUTF();
while(true){
System.out.println (str);
jta.append(str+"\n");
str = dis.readUTF();
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
public void startClient(){ //客户端启用
try{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //读取数据流
String str=br.readLine();
while(true){
sent(str); //发送数据
if(str.equals("q")){ //如果读取的字符为q
disconnect(); //断开连接
return ;
}
str=br.readLine();
}
}
catch(Exception e){
e.printStackTrace();
}
}
public void launchFrame() throws IOException{ //客户端面板布局
con = this.getContentPane();
jta = new JTextArea();
jtf = new JTextField();
final JTextField jtf1=new JTextField(10);
final JButton jb=new JButton("确认");
Panel p=new Panel();
//Panel p1=new Panel();
JLabel jl=new JLabel();
jl.setText("your name:");
//jl.getVerticalTextPosition();
jtf1.setBackground(Color.PINK);
jtf1.setBounds(10, 10, 10, 10);
p.add(jl);
p.add(jtf1);
p.add(jb);
jb.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
//e.getWhen();
e.getActionCommand();
jtf.setText(jtf1.getText().toString()+"say:");
}
});
jta.setBackground(Color.LIGHT_GRAY);
jta.setEditable(false); //不可编辑文本域
JScrollPane jsp = new JScrollPane(jta, //jta上滚动条的创建
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
//con.add(jb,BorderLayout.WEST);
con.add(jsp,BorderLayout.CENTER);
con.add(p,BorderLayout.NORTH);
con.add(jtf,BorderLayout.SOUTH);
jtf.addActionListener(new ActionListener(){ ///事件监听
public void actionPerformed(ActionEvent e){
String str = jtf.getText().toString();
Client.this.sent(str);
jtf.setText(jtf1.getText().toString()+"say:");
}
});
this.setTitle("聊天客户端,袭风版");
this.setBounds(200,200,300,400);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setBackground(Color.blue);
this.setVisible(true);
}
public static void main (String[] args) {
Client cli = new Client("127.168.1.1",3456); //创建一个客户端,并将其连接到指定 IP 地址的指定端口号,其端口号与服务器端一致。与服务器建立连接
cli.startClient(); //127.0.0.1为本机端口
}
}
package chat1;
import java.net.*;
import java.io.*;
import java.util.*;
//import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
public class Server extends JFrame{
/**
* 套接字接口可分为三类:公认端口 注册端口 动态和/或私有端口
套接字,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程
*/
private static final long serialVersionUID = 4130666237241201704L;
Container con = null; //容器
JTextArea jta = null; //文本区
ServerSocket ss = null; //初始化服务器套接字
ArrayList al = new ArrayList();//ArrayList容器
Server(int port){ //构造函数
try{
ss=new ServerSocket(port); // 创建绑定到特定端口的服务器套接字。
}
catch(Exception e){
e.printStackTrace();
}
launchFrame();
}
public void severStart(){ //线程开始
while(true){
try{
Socket s = ss.accept(); //侦听并接受到此套接字(port)的连接。
al.add(new ServerR(s));
jta.append("New Client:"+"\n Ip: "+s.getInetAddress()+":"+s.getPort()+"\n" //得到客户端本地地址,端口和客户端数量
+"Clients count:"+al.size()+"\n");
}
catch(Exception e){
e.printStackTrace();
}
}
}
class ServerR implements Runnable{
Socket s = null; //创建新服务线程
ServerR(Socket s){
this.s = s;
new Thread(this).start();//启动新线程
}
public void sent(String str){ //发送数据流方法
try{
DataOutputStream dos = new DataOutputStream(s.getOutputStream()); //从s套接字中读出输出数据流
dos.writeUTF(str); //使用 UTF-8 修改版编码将一个字符串写入基础输出流。
}
catch(Exception e){
e.printStackTrace();
}
}
public void run(){
try{
DataInputStream dis = new DataInputStream(s.getInputStream());
String str = dis.readUTF(); //读出输入的数据流
while(true){
// System.out.println (str);
Iterator ite = al.iterator(); //生成list迭代器
while(ite.hasNext()){ //如果仍有可迭代的元素,返回true
((ServerR)ite.next()).sent(str); //返回(ServerR)的下一个元素,并发送它
//先遍历ServerR中的所有线程
}
str=dis.readUTF();
}
}
catch(Exception e){
try{ //客户端关闭捕捉
s.close(); //关闭一个相关客户端线程
al.remove(this); //从迭代器指向的集合中移除迭代器返回的最后一个元素
jta.append("A client quit!\nClients count:"+al.size()+"\n"); //统计客户端现有的数量
}
catch(Exception e2){
e2.printStackTrace();
}
e.printStackTrace();
}
}
}
public void launchFrame(){ //服务器端面板布局
con = this.getContentPane();
jta = new JTextArea();
jta.setEditable(false); //不可编辑文本域
JScrollPane jsp = new JScrollPane(jta,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
con.add(jsp,BorderLayout.CENTER);
this.setTitle("聊天服务端,袭风版");
this.setBounds(200,200,300,400);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
}
public static void main (String[] args) {
Server s = new Server(3456); //创建服务器S 端口为3456
s.severStart();
}
}
iOS使用socket实现聊天功能
关于GCDAsyncSocket实现即时通讯功能
客户端需要做的:
1、连接服务器,连接socket,发送心跳
2、编码数据包,发送消息给服务器
3、接收处理服务器返回的消息
4、提供手动断联socket方法,在需要的地方调用
5、socket非手动断联,重新请求连接
服务端需要做的:
1、用户心跳的维持和刷新
2、用户的调用
3、数据包的拆解和分发等
新建socket管理类BLSocketManager
.h文件
.m文件