-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