文章目录
注意:erlang的进程式编程,用shell脚本方便管理,应使用linux来学习较好,本项目还可以继续在window环境下执行
1. 项目结构
2.学会编译erl文件至指定目录
Emakefile 文件,执行方法,在其所在的目录下打开erl终端,输入make :all() 即可编译,内容如下:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 聊天工程-处理运行命令,使用shell脚本方便管理整个工程,里面多个小模块直接使用Emakefile更加方便
%% c(模块名) 单独
%% make :all(). 使用Emakefile
%% emakefile_hd.erl Emakefile编译时处理需要复制的文件,通过erl编译文件实现
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
{
"emakefile_do.erl", [debug_info, {
i,"include"}, {
outdir,"."}]}.
{
"src/socket_chat/*", [debug_info, {
i,"include"}, {
outdir,"ebin/socket_chat"}]}.
{
"src/otp_chat/*", [debug_info, {
i,"include"}, {
outdir,"ebin/otp_chat"}]}.
{
"src/websocket_otp_chat/*", [debug_info, {
i,"include"}, {
outdir,"ebin/websocket_otp_chat"}]}.
3.编译文件目录如图
4. 如何运行,编译输出后,到底编译的文件夹目录,打开erl终端,然后就可以执行模块的方法了,如这样就启动聊天服务端了
5.项目代码说明
1.原生socket连接聊天系统
tcp聊天
tcp_server.erl 服务端
%%%-------------------------------------------------------------------
%%% @author rfos
%%% @doc
%%% tcp 服务端
%%% @end
%%% Created : 2023/8/9 19:32
%%%-------------------------------------------------------------------
-module(tcp_server).
%% API
-export([listen_socket/1, start/0]).
-define(SERVER_PORT, 8080).
start() ->
listen_socket(?SERVER_PORT),
ok.
%% 监听连接客户端
listen_socket(ServerPort) ->
{
ok, ListenSocket} = gen_tcp:listen(ServerPort, [{
active, false}, {
reuseaddr, true}]),
error_logger:info_msg("tcp 服务端 启动成功"),
accept_connections(ListenSocket).
%%处理客户端连接
accept_connections(ListenSocket) ->
accept_connections(ListenSocket, []).
accept_connections(ListenSocket, Clients) ->
{
ok, ClientSocket} = gen_tcp:accept(ListenSocket),
error_logger:info_msg("accept_connections:~w~n", [ClientSocket]),
NewClients = [{
ClientSocket, undefined} | Clients],
spawn(fun() -> handle_client(ClientSocket, NewClients) end),
accept_connections(ListenSocket, NewClients).
%%处理客户端发送的消息
handle_client(ClientSocket, Clients) ->
{
ok, SocketData} = gen_tcp:recv(ClientSocket, 0),
Res = iolist_to_binary(SocketData),
Result = binary_to_term(Res),
case Result of
{
ok, Msg} = MsgData ->
io:format("收到消息:socket:~w, msg:~ts~n", [ClientSocket, Msg]),
broadcast_message(Clients, MsgData);
Error ->
error_logger:info_msg("消息格式错误:~w", [Error])
end,
handle_client(ClientSocket, Clients).
%%广播消息-支持多人聊天
broadcast_message(Clients, Message) ->
lists:foreach(fun({
ClientSocket, _}) ->
gen_tcp:send(ClientSocket, term_to_binary(Message))
end, Clients).
tcp_client.erl 客户端
%%%-------------------------------------------------------------------
%%% @author rfos
%%% @doc
%%% tcp 客户端
%%% @end
%%% Created : 2023/8/15 18:54
%%%-------------------------------------------------------------------
-module(tcp_client).
%% API
-export([start/0]).
-define(SERVER_IP, "127.0.0.1").
-define(SERVER_PORT, 8080).
start() ->
{
ok, Socket} = gen_tcp:connect(?SERVER_IP, ?SERVER_PORT, [binary, {
packet, 0}]),
error_logger:info_msg("tcp客户端 连接成功"),
spawn(fun() -> loop_input(Socket) end),
receive_response(Socket).
loop_input(Socket) ->
MsgData = {
ok, string:trim(io:get_line("请输入你的消息: "))},
gen_tcp:send(Socket, term_to_binary(MsgData)),
loop_input(Socket).
receive_response(Socket) ->
receive
{
tcp, Socket, Bin} ->
case binary_to_term(Bin) of
{
ok, Msg} ->
io:format("(收到聊天消息):~ts~n", [Msg]);
Error1 ->
error_logger:info_msg("Error receiving response: ~w~n", [Error1])
end;
Error2 ->
error_logger:info_msg("Error receiving response: ~w~n", [Error2])
end,
receive_response(Socket).
udp聊天
udp_server.erl 服务端
%%%-------------------------------------------------------------------
%%% @author rfos
%%% @doc
%%% udp 服务端
%%% @end
%%% Created : 2023/8/9 19:32
%%%-------------------------------------------------------------------
-module(udp_server).
%% API
-export([start/0]).
-define(SERVER_IP, "127.0.0.1").
-define(SERVER_PORT, 8001).
start() ->
{
ok, ListenSocket} = gen_udp:open(?SERVER_PORT, [binary]),
error_logger:info_msg("udp 服务端 启动成功"),
receive_response(ListenSocket).
%%处理客户端连接
receive_response(Socket) ->
receive
{
udp, Socket, Host, Port, Bin} ->
case binary_to_term(Bin) of
{
ok, Msg} = MsgData ->
io:format("收到消息:~ts~n", [Msg]),
gen_udp:send(Socket, Host, Port, term_to_binary(MsgData)),
receive_response(Socket);
Error1 ->
error_logger:info_msg("Error receiving response: ~w~n", [Error1])
end;
Error2 ->
error_logger:info_msg("Error receiving response: ~w~n", [Error2])
end,
receive_response(Socket).
udp_client.erl 客户端
%%%-------------------------------------------------------------------
%%% @author rfos
%%% @doc
%%% udp 客户端
%%% @end
%%% Created : 2023/8/15 18:54
%%%-------------------------------------------------------------------
-module(udp_client).
%% API
-export([start/0]).
-define(SERVER_IP, "localhost").
-define(SERVER_PORT, 8001).
start() ->
{
ok, Socket} = gen_udp:open(0, [binary]),
error_logger:info_msg("udp客户端 连接成功"),
spawn(fun() -> loop_input(Socket) end),
receive_response(Socket).
loop_input(Socket) ->
MsgData = {
ok, string:trim(io:get_line("请输入你的消息: "))},
gen_udp:send(Socket, ?SERVER_IP, ?SERVER_PORT, term_to_binary(MsgData)),
loop_input(Socket).
receive_response(Socket) ->
receive
{
udp, Socket, _Host, _Port, Bin} ->
case binary_to_term(Bin) of
{
ok, Msg} ->
io:format("(收到聊天消息):~ts~n", [Msg]);
Error1 ->
error_logger:info_msg("Error receiving response: ~w~n", [Error1])
end;
Error2 ->
error_logger:info_msg("Error receiving response: ~w~n", [Error2])
end,
receive_response(Socket).
2.基于Otp框架搭建tcp服务端进行聊天,客户端其实同理,先自行熟悉OTP框架内容
chat.app 应用层 应用程序描述文件
在Erlang中,chat.app
文件通常是一个应用程序描述文件(.app 文件)。这个文件包含了有关 Erlang 应用程序的元数据,例如应用程序的名称、版本号、模块列表以及其他相关信息。
{
application, chat,
[{
description, "rfos - chat server!"},
{
id, "chat"},
{
vsn, "0.1"},
{
modules, [chat]},
{
registered, [chat, chat_server_sup]},
{
applications, [kernel, stdlib, sasl]},
{
mod, {
chat, []}},
{
env, []}
]}.
chat.hrl 一些普通宏定义文件
%%%-------------------------------------------------------------------
%%% @author rfos
%%% @copyright (C) 2024,
%%% @doc
%%%
%%% @end
%%% Created : 02. 四月 2024 15:01
%%%-------------------------------------------------------------------
%%日志封装
-define(INFO(Format), error_logger:info_msg(Format, [])).
-define(INFO(Format, Args), error_logger:info_msg(Format, Args)).
-define(SERVER_IP, "127.0.0.1").
-define(SERVER_PORT, 8080).
-record(state, {
socket, clients}).
-define(chat_client_list, chat_client_list). % 进程字典名称,用于存储各个聊天接口的连接,广播消息的需要[{ConnectSocket}]
-define(ROLE_PROCESS_EXIT_WAIT, 10).%%角色进程退出时等待多少秒
common_util.erl 普通工具类
%%%-------------------------------------------------------------------
%%% @author rfos
%%% @copyright (C) 2010, rfos
%%% @doc
%%% 普通类 控制
%%% @end
%%%-------------------------------------------------------------------
-module(common_util).
-include("chat.hrl").
-export([manage_applications/6, start_applications/1, stop_applications/1, copy_files/3]).
manage_applications(Iterate, Do, Undo, SkipError, ErrorTag, Apps) ->
Iterate(fun(App, Acc) ->
case Do(App) of
ok ->
?INFO("manage_applications:~ts~ts", [App, "操作成功!"]),
[App | Acc];
{
error, {
SkipError, _}} -> Acc;
{
error, Reason} ->
lists:foreach(Undo, Acc),
throw({
error, {
ErrorTag, App, Reason}})
end
end, [], Apps),
?INFO("app stop success"),