聊天编程(群聊、私聊)

-module(gameClient).
-compile(export_all).

%%功能:启动客户端
start() ->
	start_send().%%启动客户端发送消息功能

%% 进程用于接收消息
start_chat(UserId)->
	spawn(fun() -> start_receive(UserId) end).

%%功能:连接服务器(用于发送消息)
start_send() ->
	{ok, Socket} = gen_tcp:connect("localhost", 8080, [binary, {packet, 4}]),
	%% 进程用于发送消息
	register(socket_store, spawn(fun() -> store_socket(Socket) end)).
	
start_receive(UserId)->
	{ok, Socket} = gen_tcp:connect("localhost", 8080, [binary, {packet, 4}]),
	gen_tcp:send(Socket, term_to_binary({receiver,UserId})),
	receiveChatMsg(Socket).

%%功能:用于保存一个Socket,如果收到get_socket请求,则发送Socket到请求方
%%参数:Socket:保存起来的Socket
store_socket(Socket) ->
	receive
		{get_socket, From} ->
			From ! {Socket},
			store_socket(Socket)
	end.

%%功能:从store_socket进程中获取Socket
%%参数:Pid:要获取Socket的进程id
get_socket(Pid) ->
	socket_store ! {get_socket, Pid},
	receive
		{Socket} ->
			Socket
	end.

%%功能:发送消息到服务器
%%参数:Socket:连接到服务器的Socket
%%	Msg   :要发送的消息
sendmsg(Socket, Msg) ->
	gen_tcp:send(Socket, term_to_binary(Msg)).

wait()->
	receive
		die -> void
	end.
userRegister(NickName,UserId,UserPassword)->
	Msg = { userRegister,NickName,UserId,UserPassword },
	Pid = self(),
	Socket = get_socket(Pid),
	sendmsg(Socket,Msg),
	receiveMsg(Socket).  %%接收服务器返回的消息
	%%gen_tcp:close(Socket).%%注释后可以测试注册功能

userLogin(UserId,UserPassword)->
	Msg = { userLogin,UserId,UserPassword },
	Pid = self(),
	Socket = get_socket(Pid),
	sendmsg(Socket,Msg),
	receiveMsg(Socket).  %%接收服务器返回的消息

%%群聊
groupChat(UserId,NickName,Matter)->
	Msg = { groupChat,UserId,NickName,Matter},
	Pid = self(),
	Socket = get_socket(Pid),
	sendmsg(Socket,Msg),
	receiveMsg(Socket).  %%接收服务器返回的消息
%%私聊
personalChat(ReceiverId,Matter) -> 
	Msg = { personalChat,ReceiverId,Matter},
	Pid = self(),
	Socket = get_socket(Pid),
	sendmsg(Socket,Msg),
	receiveMsg(Socket).

receiveMsg(Socket) ->
	receive
		{tcp, Socket, Bin} ->
			Val = binary_to_term(Bin),
			case Val of
				{userRegister, ok} ->
					io:format("userRegister succefully!~n",[]);
				{userRegister, failed} ->
					io:format("userRegister failed!~n",[]);
				{userLogin,ok,UserId} ->
					start_chat(UserId),%%登入成功后,那么就建一个接收信息的进程
					io:format("userLogin ok!~n",[]);
				{userLogin,failed} ->
					io:format("userLogin failed!~n",[]);
				{chat, ok} ->
					io:format("sender receive message succefully!~n",[]);
				{personalChat,ok}->
					io:format("personalChat sender receive message succefully!~n")
			end			
	end.

receiveChatMsg(Socket)->
	receive
		{tcp, Socket, Bin} ->
			Val = binary_to_term(Bin),
			case Val of
				{receiver} ->
					io:format(".....sender receive message succefully!~n",[]),
 					receiveChatMsg(Socket);
				{transSend,SenderUserId,SenderNickName,SendMatter}->%%接收聊天的内容,并打印在屏幕上
					io:format("~p(~p):~p.~n", [SenderNickName,SenderUserId,SendMatter]),
					receiveChatMsg(Socket);
				{personalChat,Matter}->
					io:format(" neirong: ~p\n",[Matter]),
					receiveChatMsg(Socket)
			end
	end.
%% @author ping
%% @doc @todo Add description to gameServer.

-module(gameServer).
-export([start/0,do_this_once/0]).
-compile(export_all).
-record(user,{id,name,password}).
-record(loginUser,{id,name}).
-include_lib("stdlib/include/qlc.hrl").

do_this_once()->
	mnesia:create_schema([node()]),
	mnesia:start(),
	mnesia:create_table(user, [{attributes,record_info(fields,user)}]),
	mnesia:create_table(loginUser, [{attributes,record_info(fields,loginUser)}]),
	mnesia:stop().
start_db()->
	mnesia:start(),
	mnesia:wait_for_tables([user,loginUser], 20000).

start()->
	do_this_once(),
	start_db(),
	register(user_store, spawn(fun() -> store_user([]) end)),
	{ok, Listen} = gen_tcp:listen(8080, [binary, {packet, 4}, {reuseaddr, true}, {active, true}]),
	spawn(fun() -> par_connect(Listen) end).

par_connect(Listen) ->
	{ok, Socket} = gen_tcp:accept(Listen),
	%%%%%%%%%%%
	spawn(fun() -> par_connect(Listen) end),
	loop(Socket).

store_user(User_Online_List)->
	receive
		{addOnlineUser,socket,Socket,UserId}->
			New_User_Online_List = [{socket,Socket,UserId}|User_Online_List],
			store_user(New_User_Online_List);
		{getUserOnlineList,From}->
			From!{User_Online_List},
			store_user(User_Online_List)
	end.
	
add_User_Online(socket,Socket,UserId)->
	user_store!{addOnlineUser,socket,Socket,UserId}.

get_User_Online_List(Pid)->
	user_store!{getUserOnlineList,Pid},
	receive
		{ User_Online_List } ->
			User_Online_List
	end.

%% 增加注册的用户
addUserRegister(NickName,UserID,UserPassword) ->
	Row = #user{name=NickName, id = UserID, password = UserPassword},
	F = fun() ->
				mnesia:write(Row)
		end,
	mnesia:transaction(F).
%%查询用户是否已经被注册
queryUserRegister(UserID) ->
	do(qlc:q([{X#user.id} || X <- mnesia:table(user),X#user.id =:= UserID])).

%%用户成功登入,记录
addUserLogin(NickName, UserID)->
	Row = #loginUser{name=NickName, id = UserID},
	F = fun() ->
				mnesia:write(Row)
		end,
	mnesia:transaction(F).	

%%判断用户账号密码是否有错
queryUserLogin(UserID,UserPassword) ->
	do(qlc:q([{X#user.name,X#user.id} || X <- mnesia:table(user),
										 X#user.id =:= UserID,
										 X#user.password =:= UserPassword
			 ])).

%%查询所有用户
demo() ->
	do(qlc:q([{X#user.id} || X <- mnesia:table(user)])).
%%查询在线用户
demo(login) ->
	do(qlc:q([{X#loginUser.id} || X <- mnesia:table(loginUser)])).


do(Q) -> 
	F = fun() -> 
				qlc:e(Q) 
		end,
	{atomic,Val} = mnesia:transaction(F),
	Val.

loop(Socket) ->
	receive
		{tcp, Socket, Bin} -> %% tcp表示服务器ip和端口,Socket表示客户端ip和端口
			Context = binary_to_term(Bin),
			io:format("Server receive: Context = ~p~n", [Context]),
			case Context of
				%%匹配用户注册
				{userRegister,NickName,UserId,UserPassword}->
					Val = queryUserRegister(UserId),
					if
						Val =:= [] ->
							addUserRegister(NickName,UserId,UserPassword),
 							Reply = { userRegister,ok };
						true->
							Reply = { userRegister,failed }
					end;
				%%匹配用户登入
				{userLogin,UserId,UserPassword}->
					Val = queryUserLogin(UserId,UserPassword),   
					if
						Val =:= [] ->							
 							Reply = { userLogin,failed };
						true->
							[{NickName, UserId}] = Val,
							addUserLogin(NickName, UserId),
							Reply = { userLogin,ok,UserId }
					end;
				{ groupChat,UserId,NickName,Matter} ->%%群聊
					Pid = self(),
					User_Online_list = get_User_Online_List(Pid),
					ok = transSend(User_Online_list,UserId,NickName,Matter),%%把聊天内容转发给各个已登录的用户
					Reply = {chat, ok};
				{ personalChat,ReceiverId,Matter} ->%%私聊
					Pid = self(),
					User_Online_list = get_User_Online_List(Pid),
					ok = personalChatTransSend(User_Online_list,ReceiverId,Matter),%%把聊天内容转发给各个已登录的用户
					Reply = {personalChat, ok};
				{ receiver,UserId } ->
					add_User_Online(socket,Socket,UserId), %%记录用户在线
					Reply = {receiver}
			end,
			gen_tcp:send(Socket, term_to_binary(Reply)),
			loop(Socket);
		{tcp_closed, Socket} ->
			io:format("Client socket closed~n")
	end.

personalChatTransSend([],_,_) -> ok;
personalChatTransSend([H|T],ReceiverId,Matter)->
	case H of
		{socket,Socket,UserId} ->%%列表里的内容是Socket
			if
				UserId =:= ReceiverId->
					io:format("is myself!\n"),
					gen_tcp:send(Socket, term_to_binary({personalChat,Matter}));
				true->
					io:format("other person!\n")
			end,
			personalChatTransSend(T,ReceiverId,Matter)
									  
%% 		_ ->
%% 			io:format("before transmit!	H = ~p~n", [H]),
%% 			wrong
	end.
	

transSend([], _, _,_) -> ok;
transSend([H|T],UserId,NickName,Matter) ->
	case H of
		{socket,Socket,ReceiveUserId} ->%%列表里的内容是Socket
			if
				ReceiveUserId =:= UserId->
					io:format("transSend is myself!\n");
				true->
					io:format("before transmit!	H = ~p~n", [H]),
					gen_tcp:send(Socket, term_to_binary({transSend,UserId,NickName,Matter}))
					%% ListUserId 接收者账号
					%% UserId 发送者账号
					%% NickName 发送者昵称
					%% Matter 发送者发送的内容
			end,
			transSend(T, UserId,NickName,Matter)
%% 		_ ->
%% 			io:format("before transmit!	H = ~p~n", [H]),
%% 			wrong
	end.	

1、mnesia数据表的第一个元素是主键,不可以出现重复,否则出现覆盖

2、和数据查询的结果匹配模式
case demo(UserID) of 
	[{UserID}] ->     %% 判断是否已经被注册
		Reply = { userRegister,failed };
	_->
		add_user(NickName,UserID,UserPassword),
		Reply = { userRegister,ok }
end.

3、服务器端口与客户端的关系不明白
	多个客户端连接一个服务器端口8080,在服务器上有一个相应的进程监听Listen,
	然后新建进程 accept( 监听)的信息,然后在该进程的loop函数匹配,直到进程消亡
{ok, Listen} = gen_tcp:listen(8080, [binary, {packet, 4}, {reuseaddr, true}, {active, true}]),
spawn(fun() -> par_connect(Listen) end).
	
par_connect(Listen) ->
	{ok, Socket} = gen_tcp:accept(Listen),
	spawn(fun() -> par_connect(Listen) end),
	loop(Socket).
	一个accept对应一个进程,该进程是异步的
	
4、多个客户端同时请求服务器数据时,某一时间服务器返回某个客户端数据时,会错发给了别的客户端么?
	tcp是四元组的连接...
	四元组是:  源IP地址、目的IP地址、源端口、目的端口
	五元组是: 源IP地址、目的IP地址、协议号、源端口、目的端口
	七元组是: 源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型以及接口索引

5、客户端上连接服务器的注册进程,将一直存活,除非发送了{tcp_closed, Socket}信号

6、聊天要转发内容,则需要在服务器上执行,服务器专门保存了每个客户端的socket(客户端ip,客户端端口),
	转发内容时则直接获取其他客户端的Socket
	
7、不同的进程接收Socket是不一样的,客户端有接收信息进程和发送信息进程

8、将汉字写进文本
Bin2 = unicode:characters_to_binary("你好", utf8),
Test = unicode:characters_to_list(Bin2),
file:write_file("test.txt", Test, [append]).

Eshell V5.10.2
(Game1@ping-PC)1> gameServer:start().
<0.207.0>
(Game1@ping-PC)2> gameClient:start().
true
(Game1@ping-PC)3> gameClient:userRegister(1, 1, 1).
Server receive: Context = {userRegister,1,1,1}
userRegister succefully!
ok
(Game1@ping-PC)4> gameClient:userRegister(2, 2, 2).
Server receive: Context = {userRegister,2,2,2}
userRegister succefully!
ok
(Game1@ping-PC)5> gameClient:userRegister(3, 3, 3).
Server receive: Context = {userRegister,3,3,3}
userRegister succefully!
ok
(Game1@ping-PC)6> gameClient:userLogin(1, 1).
Server receive: Context = {userLogin,1,1}
userLogin ok!
ok
(Game1@ping-PC)7> Server receive: Context = {receiver,1}
.....sender receive message succefully!
gameClient:userLogin(2, 2).
Server receive: Context = {userLogin,2,2}
userLogin ok!
ok
(Game1@ping-PC)8> Server receive: Context = {receiver,2}
.....sender receive message succefully!
gameClient:userLogin(3, 3).
Server receive: Context = {userLogin,3,3}
userLogin ok!
ok
(Game1@ping-PC)9> Server receive: Context = {receiver,3}
.....sender receive message succefully!
gameClient:groupChat(1, 1, 1).
Server receive: Context = {groupChat,1,1,1}
before transmit!	H = {socket,#Port<0.1838>,3}
before transmit!	H = {socket,#Port<0.1836>,2}
transSend is myself!
1(1):1.
1(1):1.
sender receive message succefully!
ok
(Game1@ping-PC)10> gameClient:personalChat(2, 2).
Server receive: Context = {personalChat,2,2}
other person!
is myself!
other person!
 neirong: 2
personalChat sender receive message succefully!
ok


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值