zoukankan      html  css  js  c++  java
  • Erlang聊天室

    Erlang 聊天室

    因为之前有使用过PyQt做过聊天室的功能,现在使用Erlang去重写聊天室,去了解这门语言,有兴趣的朋友可以写基于Qt客户端和Erlang的服务端去交互,本Demo统一使用Erlang去实现客户端。

    所需知识

    • 网络编程中接受请求的套接字创建过程如下(四步记忆)
      • 第一步:调用socket函数
      • 第二步:调用bind函数分配IP地址和端口
      • 第三步:调用listen函数转为可接收请求状态
      • 第四步:调用accept函数受理连接请求

    服务端监听Socket

    • 第一部分初始化ets表,可选参数可自由选择
    start_server() ->
    	ets:new(id, [ordered_set, public, named_table, {write_concurrency, true}, {read_concurrency, true}]),
    	case gen_tcp:listen(1234, [binary, {packet, 0}, {active, true}]) of
    		{ok, ListenSocket} ->
    			spawn(fun() -> client_connect(ListenSocket) end);
    		{error, Reason} ->
    			io:format("~p~n", [Reason])
    	end.
    
    • 第二部分处理请求
      • 这部分要注意gen_tcp:accept()函数,这部分会处理本次请求才会去处理新的请求,即顺序处理,为了解决这个问题,使用了并发处理,每次连接都会开一个新进程去处理,这样就可以并发处理了。
    client_connect(ListenSocket) ->
    	case gen_tcp:accept(ListenSocket) of
    		{ok, Socket} ->
    			%% 进行验证,看是否是注册还是登录
    			spawn(fun() -> client_connect(ListenSocket) end),
    			loop(Socket);
    		{error, Reason} ->
    			io:format("~p~n", [Reason])
    	end.
    
    • 第三部分While True循环
      • 这部分分别去处理客户端的发送信息,如注册,登录,退出,私聊,群聊。
    loop(Socket) ->
    	receive
    		{tcp, Socket, Bin} ->
    			[Id, Sign, PassWord, SendId, MessageInfos] = binary_to_term(Bin),
    			if
    				Sign =:= register_user ->
    					Info = register_user(Id, PassWord, Socket),
    					gen_tcp:send(Socket, term_to_binary(Info)),
    					loop(Socket);
    				Sign =:= login_user ->
    					Info = login_user(Id, PassWord, Socket),
    					gen_tcp:send(Socket, term_to_binary(Info)),
    					loop(Socket);
    				Sign =:= login_out ->
    					Info = login_out(Id, Socket),
    					gen_tcp:send(Socket, term_to_binary(Info)),
    					loop(Socket);
    				Sign =:= private_msg ->
    					private_chat(SendId, Socket, MessageInfos),
    					loop(Socket);
    				Sign =:= group_msg ->
    					group_chat(Socket, MessageInfos),
    					loop(Socket);
    				true ->
    					io:format("error sign ~n"),
    					loop(Socket)
    			end;
    		{tcp_closed, Socket} ->
    			io:format("Server socket closed ~n")
    	end.
    
    • 功能部分

      • 用户注册
      • 使用ets库的lookup函数去检查用户是否已经注册
      %% 用户注册
      register_user(Id, PassWord, Socket) ->
      	case ets:lookup(id, Id) of
      		[_Ok] ->
      			io:format("Account is fail ~n"),
      			"Account is exist ~n";
      		_ ->
      			ets:insert(id, {Id, PassWord, 0, Socket}),
      			"register successed ~n"
      	end.
      
      • 用户登录
      • 首先使用ets库的match_object函数的去检查用户登录情况,这里其实写详细点,就是有三种可能,用户账号不存在,密码错误,用户已经在线。同时如果登录成功,应该设置用户为在线状态和给用户分配socket。
      %% 用户登录
      login_user(Id, PassWord, Socket) ->
      	case ets:match_object(id, {Id, PassWord, 0, Socket}) of
      		[_Ok] ->
      			ets:update_element(id, Id, [{3, 1}, {4, Socket}]),
      			"login successed";
      		Reson ->
      			io:format("login is fail ~n ~p", [Reson]),
      			"Password error or Account is not exist ~n"
      	end.
      
      • 用户退出
      • 退出用户,首先判断用户已经在线,才能下线,同时把在线状态和socket设置为0。
      %% 退出用户
      login_out(Id, Socket) ->
      	%% 因为id对应唯一socket,所以不需要PassWord
      	case ets:match_object(id, {Id, '_', 1, Socket}) of
      		[_Ok] ->
      			ets:update_element(id, Id, [{3, 0}, {4, 0}]),
      			"login successed";
      		_ ->
      			io:format("out is fail ~n"),
      			"login is fail"
      	end.
      
      • 群聊
      • 首先判断发送用户是否在线,然后递归给在线用户发送信息。
      %% 群聊
      group_chat(Socket, MessageInfos) ->
      	case ets:match_object(id, {'_', '_', 1, Socket}) of
      		[{Id, _, _, _}] ->
      			Res = ets:match_object(id, {'_', '_', 1, '_'}),
      			case Res =:= [] of
      				true ->
      					io:format("no person online ~p ~n", [Res]);
      				_ ->
      					group_send_msg(Res, Id, MessageInfos)
      			end;
      		_ ->
      			io:format("group chat is fail ~n")
      	end.
      
      
      %% 群聊发送
      group_send_msg([], _Id, _MessageInfos) ->
      	next;
      group_send_msg([Info | Infos], Id, MessageInfos) ->
      	{_, _, _, Socket} = Info,
      	gen_tcp:send(Socket, term_to_binary("from: " ++ integer_to_list(Id) ++ "say: " ++ MessageInfos)),
      	group_send_msg(Infos, Id, MessageInfos).
      
      • 在线私聊
      • 由于没有设置字段去存取聊天记录,所以使用只有在线状态下的聊天。
      %% 在线私聊
      private_chat(SendId, Socket, MessageInfos) ->
      	case ets:match_object(id, {'_', '_', 1, Socket}) of
      		[{Id, _, _, _}] ->
      			Res = ets:match_object(id, {SendId, '_', 1, '_'}),
      			case Res =:= [] of
      				true ->
      					io:format("send person not online ~p ~n", [Res]);
      				_ ->
      					private_send_msg(Res, Id, MessageInfos)
      			end;
      		_ ->
      			io:format("private chat is fail ~n")
      	end.
      
      %% 私聊发送
      private_send_msg([Info], Id, MessageInfos) ->
      	{_, _, _, Socket} = Info,
      	gen_tcp:send(Socket, term_to_binary("from: " ++ integer_to_list(Id) ++ "say: " ++ MessageInfos)).
      

    客户端实现

    • 客户端进程
    • 并把socket控制进程设置为loop的进程号
    %客户端
    start_client() ->
    	{ok, Socket} = gen_tcp:connect("localhost", 1234, [binary, {packet, 0}]),  %连接服务器
    	%新建一个进程负责接收消息
    	Pid = spawn(fun() -> loop() end),
    	gen_tcp:controlling_process(Socket, Pid),
    	sendMsg(Socket).
    
    loop() ->
    	receive
    		{tcp, _Socket, Bin} ->
    			Res = binary_to_term(Bin),
    			io:format("Message Info! ~p ~n", [Res]),
    			loop();
    		{tcp_closed, _Socket} ->
    			io:format("Socket is closed! ~p ~n")
    	end.
    
    • 客户端操作
    • 对应服务端的功能部分
    sendMsg(Socket) ->
    	S = io:get_line("select operation: "),
    	{Sign, _Info} = string:to_integer(S),
    	SendMsg = operation_message(Sign),
    	gen_tcp:send(Socket, term_to_binary(SendMsg)),
    	sendMsg(Socket).
    
    %% 用户注册
    operation_message(1) ->
    	I = io:get_line("id: "),
    	{Id, _Info} = string:to_integer(I),
    	Password = io:get_line("register password: "),
    	[Id, register_user, Password, 0, 0];
    %% 用户登录
    operation_message(2) ->
    	I = io:get_line("id:"),
    	Password = io:get_line("login password: "),
    	{Id, _Info} = string:to_integer(I),
    	[Id, login_user, Password, 0, 0];
    %% 用户退出
    operation_message(3) ->
    	I = io:get_line("id: "),
    	{Id, _Info} = string:to_integer(I),
    	[Id, login_out, 0, 0, 0];
    %% 私聊
    operation_message(4) ->
    	Sd = io:get_line("send_id: "),
    	Msg = io:get_line("MsgInfo: "),
    	{SendId, _Info} = string:to_integer(Sd),
    	[0, private_msg, 0, SendId, Msg];
    %% 群聊
    operation_message(5) ->
    	Msg = io:get_line("MsgInfo: "),
    	[0, group_msg, 0, 0, Msg];
    %% 无效操作
    operation_message(_) ->
    	Msg = io:format("invalid_operation ~n"),
    	[0, invalid_operation, 0, 0, Msg].
    

    完整源代码

    • 服务端
    %%%-------------------------------------------------------------------
    %%% @author Curry
    %%% @copyright (C) 2021, <COMPANY>
    %%% @doc
    %%%
    %%% @end
    %%% Created : 08. 8月 2021 13:33
    %%%-------------------------------------------------------------------
    -module(chatserv).
    -author("Curry").
    
    %% API
    -compile(export_all).
    -import(ets, [insert_new/2]).
    
    start_server() ->
    	ets:new(id, [ordered_set, public, named_table, {write_concurrency, true}, {read_concurrency, true}]),
    	case gen_tcp:listen(1234, [binary, {packet, 0}, {active, true}]) of
    		{ok, ListenSocket} ->
    			spawn(fun() -> client_connect(ListenSocket) end);
    		{error, Reason} ->
    			io:format("~p~n", [Reason])
    	end.
    
    
    client_connect(ListenSocket) ->
    	case gen_tcp:accept(ListenSocket) of
    		{ok, Socket} ->
    			%% 进行验证,看是否是注册还是登录
    			spawn(fun() -> client_connect(ListenSocket) end),
    			loop(Socket);
    		{error, Reason} ->
    			io:format("~p~n", [Reason])
    	end.
    
    loop(Socket) ->
    	receive
    		{tcp, Socket, Bin} ->
    			[Id, Sign, PassWord, SendId, MessageInfos] = binary_to_term(Bin),
    			if
    				Sign =:= register_user ->
    					Info = register_user(Id, PassWord, Socket),
    					gen_tcp:send(Socket, term_to_binary(Info)),
    					loop(Socket);
    				Sign =:= login_user ->
    					Info = login_user(Id, PassWord, Socket),
    					gen_tcp:send(Socket, term_to_binary(Info)),
    					loop(Socket);
    				Sign =:= login_out ->
    					Info = login_out(Id, Socket),
    					gen_tcp:send(Socket, term_to_binary(Info)),
    					loop(Socket);
    				Sign =:= private_msg ->
    					private_chat(SendId, Socket, MessageInfos),
    					loop(Socket);
    				Sign =:= group_msg ->
    					group_chat(Socket, MessageInfos),
    					loop(Socket);
    				true ->
    					io:format("error sign ~n"),
    					loop(Socket)
    			end;
    		{tcp_closed, Socket} ->
    			io:format("Server socket closed ~n")
    	end.
    
    %% 用户注册
    register_user(Id, PassWord, Socket) ->
    	case ets:lookup(id, Id) of
    		[_Ok] ->
    			io:format("Account is fail ~n"),
    			"Account is exist ~n";
    		_ ->
    			ets:insert(id, {Id, PassWord, 0, Socket}),
    			"register successed ~n"
    	end.
    
    %% 用户登录
    login_user(Id, PassWord, Socket) ->
    	case ets:match_object(id, {Id, PassWord, 0, Socket}) of
    		[_Ok] ->
    			ets:update_element(id, Id, [{3, 1}, {4, Socket}]),
    			"login successed";
    		Reson ->
    			io:format("login is fail ~n ~p", [Reson]),
    			"Password error or Account is not exist ~n"
    	end.
    
    %% 退出用户
    login_out(Id, Socket) ->
    	%% 因为id对应唯一socket,所以不需要PassWord
    	case ets:match_object(id, {Id, '_', 1, Socket}) of
    		[_Ok] ->
    			ets:update_element(id, Id, [{3, 0}, {4, 0}]),
    			"login successed";
    		_ ->
    			io:format("out is fail ~n"),
    			"login is fail"
    	end.
    
    %% 群聊
    group_chat(Socket, MessageInfos) ->
    	case ets:match_object(id, {'_', '_', 1, Socket}) of
    		[{Id, _, _, _}] ->
    			Res = ets:match_object(id, {'_', '_', 1, '_'}),
    			case Res =:= [] of
    				true ->
    					io:format("no person online ~p ~n", [Res]);
    				_ ->
    					group_send_msg(Res, Id, MessageInfos)
    			end;
    		_ ->
    			io:format("group chat is fail ~n")
    	end.
    
    
    %% 群聊发送
    group_send_msg([], _Id, _MessageInfos) ->
    	next;
    group_send_msg([Info | Infos], Id, MessageInfos) ->
    	{_, _, _, Socket} = Info,
    	gen_tcp:send(Socket, term_to_binary("from: " ++ integer_to_list(Id) ++ "say: " ++ MessageInfos)),
    	group_send_msg(Infos, Id, MessageInfos).
    
    %% 在线私聊
    private_chat(SendId, Socket, MessageInfos) ->
    	case ets:match_object(id, {'_', '_', 1, Socket}) of
    		[{Id, _, _, _}] ->
    			Res = ets:match_object(id, {SendId, '_', 1, '_'}),
    			case Res =:= [] of
    				true ->
    					io:format("send person not online ~p ~n", [Res]);
    				_ ->
    					private_send_msg(Res, Id, MessageInfos)
    			end;
    		_ ->
    			io:format("private chat is fail ~n")
    	end.
    
    %% 私聊发送
    private_send_msg([Info], Id, MessageInfos) ->
    	{_, _, _, Socket} = Info,
    	gen_tcp:send(Socket, term_to_binary("from: " ++ integer_to_list(Id) ++ "say: " ++ MessageInfos)).
    
    • 客户端
    %%%-------------------------------------------------------------------
    %%% @author Curry
    %%% @copyright (C) 2021, <COMPANY>
    %%% @doc
    %%%
    %%% @end
    %%% Created : 08. 8月 2021 14:03
    %%%-------------------------------------------------------------------
    -module(chatclient).
    -author("Curry").
    
    %% API
    -compile(export_all).
    
    %客户端
    start_client() ->
    	{ok, Socket} = gen_tcp:connect("localhost", 1234, [binary, {packet, 0}]),  %连接服务器
    	%新建一个进程负责接收消息
    	Pid = spawn(fun() -> loop() end),
    	gen_tcp:controlling_process(Socket, Pid),
    	sendMsg(Socket).
    
    loop() ->
    	receive
    		{tcp, _Socket, Bin} ->
    			Res = binary_to_term(Bin),
    			io:format("Message Info! ~p ~n", [Res]),
    			loop();
    		{tcp_closed, _Socket} ->
    			io:format("Socket is closed! ~p ~n")
    	end.
    
    sendMsg(Socket) ->
    	S = io:get_line("select operation: "),
    	{Sign, _Info} = string:to_integer(S),
    	SendMsg = operation_message(Sign),
    	gen_tcp:send(Socket, term_to_binary(SendMsg)),
    	sendMsg(Socket).
    
    %% 用户注册
    operation_message(1) ->
    	I = io:get_line("id: "),
    	{Id, _Info} = string:to_integer(I),
    	Password = io:get_line("register password: "),
    	[Id, register_user, Password, 0, 0];
    %% 用户登录
    operation_message(2) ->
    	I = io:get_line("id:"),
    	Password = io:get_line("login password: "),
    	{Id, _Info} = string:to_integer(I),
    	[Id, login_user, Password, 0, 0];
    %% 用户退出
    operation_message(3) ->
    	I = io:get_line("id: "),
    	{Id, _Info} = string:to_integer(I),
    	[Id, login_out, 0, 0, 0];
    %% 私聊
    operation_message(4) ->
    	Sd = io:get_line("send_id: "),
    	Msg = io:get_line("MsgInfo: "),
    	{SendId, _Info} = string:to_integer(Sd),
    	[0, private_msg, 0, SendId, Msg];
    %% 群聊
    operation_message(5) ->
    	Msg = io:get_line("MsgInfo: "),
    	[0, group_msg, 0, 0, Msg];
    %% 无效操作
    operation_message(_) ->
    	Msg = io:format("invalid_operation ~n"),
    	[0, invalid_operation, 0, 0, Msg].
    
  • 相关阅读:
    lightoj-1047
    lightoj-1044
    lightoj-1045
    lightoj-1082
    LeetCode偶尔一题 —— 19. 删除链表的倒数第N个节点
    Python 3.52官方文档翻译 http://usyiyi.cn/translate/python_352/library/index.html 必看!
    Python3 time模块
    JavaScript CSS 等前端推荐
    Python之 七级字典查询
    将Sublime Text 3设置为Python全栈开发环境(转一个链接)
  • 原文地址:https://www.cnblogs.com/572354941hnit/p/15115880.html
Copyright © 2011-2022 走看看