program Project1;
{$APPTYPE CONSOLE}
uses
windows,
Messages,
SysUtils,
Variants,
Classes,
Winsock2,
ChartoUnicode in 'ChartoUnicode.pas';
type
Runner = function(sc: TSocket; argc: integer; argv: array of Pchar): integer;
// 函数指针
type
command = record
cmd: Pansichar;
run: Runner;
end;
var
wsadata: Twsadata;
slisten: TSocket;
i: integer;
bStop: Boolean = FALSE;
bok: Boolean = FALSE;
ID: DWORD;
ch: char;
//命令函数
function show_time(sc: TSocket; argc: integer; argv: array of Pchar): integer;
var
stime: SYSTEMTIME;
buf: array [0 .. 1024] of char;
len: integer;
str: string;
begin
GetSystemTime(stime);
str := format('%d-%d-%d %d:%d:%d', [stime.wYear, stime.wMonth, stime.wDay, stime.wHour, stime.wMinute, stime.wSecond]);
FillChar(buf, sizeof(buf), '');
StrPCopy(buf, str);
len := send(sc, buf, Length(buf), 0);
Result := len;
end;
function good_bay(sc: TSocket; argc: integer; argv: array of Pchar): integer;
begin
closesocket(sc);
Result := 0;
end;
// 会话线程
// 所有关系到收发数据的缓冲都属于简单的char类型。也就是说,这些函数没有“Unicode”版本。
function TalkThread(p: Pointer): DWORD; stdcall;
const
cmd_time: command = (cmd: 'time'; run: show_time);
cmd_bye: command = (cmd: 'bye'; run: good_bay);
// g_cmd_tab: array [0 .. 1] of command = (cmd_time, cmd_bye);
var
sclient: TSocket;
len, pos: integer;
buffer: array [0 .. 1024] of char;
c: char;
bok, brun: Boolean;
str: string;
argc: integer;
argv: array [0 .. 100] of Pchar;
g_cmd_tab: array [0 .. 1] of command;
// 关于记录型数组赋值
// const
// a1:command=(cmd:'123';run:show_time);
// 如果其参数为记录数组(如下),那我将如何按上述样式传参
// procedure Cell(cells :array of command);
// 应用Cell([a1,a2]);
begin
sclient := TSocket(p);
bok := True;
while (CompareText(buffer, 'exit') <> 0) do // 循环收发交互
begin
// ①服务器先发至人
FillChar(buffer, sizeof(buffer), '');
StrPCopy(buffer, 'F:\Socket>'); // xtractFilePath(ParamStr(0))
len := send(sclient, buffer, Length(buffer), 0);
if len <= 0 then
begin
bok := FALSE;
break;
end;
// ②客户端发话
pos := 0;
FillChar(buffer, sizeof(buffer), '');
c := #0;
while (c <> #32) do // 按Space结束
begin
len := recv(sclient, c, sizeof(c), 0);
if len = 0 then
begin
bok := FALSE;
break;
end;
//if (c <> #32) then // 字符加入数组buffer
begin
buffer[pos] := c;
Inc(pos);
end;
end;
if not bok then
begin
break;
end;
Writeln('客户端说:' + buffer); // recv(sclient, buffer, length(buffer), 0)
c := #10;
send(sclient, c, Length(c), 0); // 显示回车
c := #13;
send(sclient, c, Length(c), 0);
// 实例化
argc := 1;
argv[0] := buffer;
g_cmd_tab[0] := cmd_time;
g_cmd_tab[1] := cmd_bye;
// g_cmd_tab[0].cmd := 'time';
// g_cmd_tab[0].run := show_time;
// g_cmd_tab[1].cmd := 'bye';
// g_cmd_tab[1].run := good_bay;
// ③服务器回应
if argv[0] <> 'exit' then
begin
i := (sizeof(g_cmd_tab)) div (sizeof(g_cmd_tab[0]));
brun := FALSE;
for pos := 0 to i - 1 do
begin
if (CompareText(argv[0], g_cmd_tab[pos].cmd) = 0) then
begin
g_cmd_tab[pos].run(sclient, argc, argv);
brun := True;
break;
end;
end;
if brun = FALSE then
begin
StrPCopy(buffer, 'input erro!');
send(sclient, buffer, Length(buffer), 0);
end;
c := #10;
send(sclient, c, Length(c), 0);
c := #13;
send(sclient, c, Length(c), 0);
end; { if }
end; { while }
closesocket(sclient);
Result := 0;
end;
// 循环侦听线程
function ListenThread(p: Pointer): DWORD; stdcall;
var
slisten, sclient: TSocket;
addr: TSockAddrIn;
IP: ansistring;
len: integer;
IDD: DWORD;
begin
slisten := TSocket(p);
len := sizeof(addr);
// 用一个循环来反复判断是否有客户端请求,如果存在请求就创建一个用来接受数据的读取线程
while (bStop = FALSE) do
begin
sclient := accept(slisten, PSockAddr(@addr), @(len));
if sclient <> INVALID_SOCKET then // 存在请求
begin
IP := inet_ntoa(addr.sin_addr);
Writeln(IP + '已经连接' + ',端口号是' + inttostr(ntohs(addr.sin_port)));
CloseHandle(CreateThread(nil, 0, @TalkThread, Pointer(sclient), 0, IDD));
end;
end;
Result := 0;
end;
function BindPort(s: TSocket; w: word): integer;
var
addr: TSockAddrIn;
HostEnt: PHostEnt;
buffer: array [0 .. 63] of Ansichar;
IP: ansistring;
begin
ZeroMemory(@addr, sizeof(addr));
addr.sin_family := AF_INET;
addr.sin_port := htons(w);
// addr.sin_addr.S_addr := htonl(INADDR_ANY);;
// /// ///
GetHostName(buffer, sizeof(buffer));
HostEnt := GetHostByName(buffer); // h_addr_list: PPAnsiChar
// IP := StrPas(inet_ntoa(PInAddr(HostEnt.h_addr_list^)^));
IP := StrPas(inet_ntoa(TInAddr(PInAddr(HostEnt.h_addr_list^)^)));
// /// ///
addr.sin_addr.S_addr := inet_addr(Pansichar(IP));
Writeln('服务器IP:' + IP + ',端口号是' + inttostr(ntohs(addr.sin_port)));
Result := bind(s, (PSockAddr(@addr))^, sizeof(addr));
end;
// main
begin
{ TODO -oUser -cConsole Main : Insert code here }
if (WSAStartup(MAKEWORD(2, 2), wsadata)) <> 0 then
begin
Writeln('无法在本台计算机上启动Socket!');
end
else
begin
slisten := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if slisten = INVALID_SOCKET then
begin
Writeln('无法建立Socket!');
i := WSAGetLastError;
end
else
begin
if BindPort(slisten, 8986) = SOCKET_ERROR then
begin
Writeln('无法绑定端口!');
i := WSAGetLastError;
end
else
begin
if listen(slisten, 100) = SOCKET_ERROR then
begin
Writeln('无法启动侦听');
end
else
begin
Writeln('启动侦听!');
CloseHandle(CreateThread(nil, 0, @ListenThread,
Pointer(slisten), 0, ID));
Writeln('按回车键结束');
Readln(ch); // 输入字符
if ch = #13 then
begin
bStop := True;
closesocket(slisten);
end;
end;
end;
end;
WSACleanup();
end;
end.
1 面向连接的套接字的系统调用时序图
服务端代码:
unit ServerSocket;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, winSock2;
type
TMyServer = class(TForm)
StatusBar1: TStatusBar;
Memo1: TMemo;
edt_send: TEdit;
btn_create: TButton;
btn_sender: TButton;
Button1: TButton;
procedure btn_createClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure btn_senderClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MyServer: TMyServer;
var
slisten: TSocket;
sclient: TSocket;
i: integer;
bStop: Boolean = FALSE;
bok: Boolean = FALSE;
implementation
{$R *.dfm}
// function TalkThread(p: Pointer): DWORD; stdcall;
// var
// addr: TsockAddrin;
// IP: ansistring;
// buffer: array [0 .. 1024] of char;
// ch: char;
// len, pos: integer;
// begin
// sclient := TSocket(p);
// while bok = FALSE do
// begin
// fillchar(buffer, sizeof(buffer), '');
// len := recv(sclient, buffer, Length(buffer), 0);
// if len > 0 then
// begin
// MyServer.Memo1.Lines.Add('客户端说:' + buffer);
// end;
// end;
// result := 0;
// end;
// 循环侦听线程
function ListenThread(p: Pointer): DWORD; stdcall;
var
addr: TsockAddrin;
IP: ansistring;
buffer: array [0 .. 63] of char;
len: integer;
begin
fillchar(buffer, sizeof(buffer), '');
slisten := TSocket(p);
len := sizeof(addr);
// 用一个循环来反复判断是否有客户端请求,如果存在请求就创建一个用来接受数据的读取线程
while (bStop = FALSE) do
begin
sclient := accept(slisten, PSockAddr(@addr), @(len));
if sclient <> INVALID_SOCKET then // 存在请求
begin
IP := inet_ntoa(addr.sin_addr);
MyServer.StatusBar1.Panels[0].Text := IP + '已经连接' + ',端口号是' + inttostr(ntohs(addr.sin_port));
while bok = FALSE do
begin
len := recv(sclient, buffer, Length(buffer), 0);
if len > 0 then
begin
MyServer.Memo1.Lines.Add('客户端说:' + buffer);
end;
end;
end;
end;
result := 0;
end;
function BindPort(s: TSocket; w: word): integer;
var
addr: TsockAddrin;
HostEnt: PHostEnt;
buffer: array [0 .. 63] of ansichar;
IP: ansistring;
begin
ZeroMemory(@addr, sizeof(addr));
addr.sin_family := AF_INET;
addr.sin_port := htons(w);
// addr.sin_addr.S_addr := htonl(INADDR_ANY);;
GetHostName(buffer, sizeof(buffer));
HostEnt := GetHostByName(buffer); // h_addr_list: PPAnsiChar
IP := StrPas(inet_ntoa(TInAddr(PInAddr(HostEnt.h_addr_list^)^)));
addr.sin_addr.S_addr := inet_addr(Pansichar(IP));
result := bind(s, (PSockAddr(@addr))^, sizeof(addr));
end;
procedure TMyServer.btn_createClick(Sender: TObject);
var
wsadata: Twsadata;
ID: DWORD;
begin
if (WSAStartup(MAKEWORD(2, 2), wsadata)) <> 0 then
begin
ShowMessage('无法在本台计算机上启动Socket!');
end
else
begin
slisten := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if slisten = INVALID_SOCKET then
begin
ShowMessage('无法建立Socket!');
i := WSAGetLastError;
end
else
begin
if BindPort(slisten, 8986) = SOCKET_ERROR then
begin
ShowMessage('无法绑定端口!');
i := WSAGetLastError;
end
else
begin
if listen(slisten, 100) = SOCKET_ERROR then
begin
ShowMessage('无法启动侦听');
end
else
begin
StatusBar1.Panels[0].Text := '启动侦听!';
CloseHandle(CreateThread(nil, 0, @ListenThread,
Pointer(slisten), 0, ID));
end;
end;
end;
end;
end;
procedure TMyServer.btn_senderClick(Sender: TObject);
var
len: integer;
buffer: array [0 .. 1024] of char;
begin
fillchar(buffer, sizeof(buffer), '');
bStop := True;
if bStop = True then
begin
StrPCopy(buffer, edt_send.Text);
send(sclient, buffer, Length(buffer), 0);
Memo1.Lines.Add('服务端说:' + buffer);
end;
end;
procedure TMyServer.Button1Click(Sender: TObject);
begin
closesocket(slisten);
closesocket(sclient);
WSACleanup();
StatusBar1.Panels[0].Text := '关闭侦听!';
end;
end.
客户端代码:
unit ClientSocket;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, winsock2;
type
TMyClient = class(TForm)
StatusBar1: TStatusBar;
Memo1: TMemo;
edt_send: TEdit;
btn_send: TButton;
btn_link: TButton;
Button1: TButton;
procedure btn_sendClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure btn_linkClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MyClient: TMyClient;
client: Tsocket;
bStop: Boolean = false;
implementation
{$R *.dfm}
function TalkeThread(p: Pointer): DWORD; stdcall;
var
addr: TSockAddrIn;
IP: ansistring;
buffer: array [0 .. 1024] of char;
ch: char;
len, pos: integer;
begin
FillChar(buffer, sizeof(buffer), '');
pos := 0;
while bStop = false do
begin
len := recv(client, ch, sizeof(ch), 0);
if len > 0 then
begin
if ch <> #32 then // 字符加入数组buffer
begin
buffer[pos] := ch;
Inc(pos);
end;
end;
end;
if length(buffer) <> 0 then
begin
MyClient.Memo1.Lines.Add('服务端说:' + buffer);
end;
Result := 0;
end;
procedure TMyClient.btn_linkClick(Sender: TObject);
var
wsadata: Twsadata;
addr: TSockAddrIn;
HostEnt: PHostEnt;
buffer: array [0 .. 63] of AnsiChar;
IP: ansistring;
ID: DWORD;
i, len: integer;
begin
if (WSAStartup(MAKEWORD(2, 2), wsadata)) <> 0 then
begin
ShowMessage('无法在本台计算机上启动Socket!');
end
else
begin
client := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if client = INVALID_SOCKET then
begin
ShowMessage('无法建立Socket!');
i := WSAGetLastError;
end
else
begin
ZeroMemory(@addr, sizeof(addr));
addr.sin_family := AF_INET;
addr.sin_port := htons(8986);
GetHostName(buffer, sizeof(buffer));
HostEnt := GetHostByName(buffer);
IP := StrPas(inet_ntoa(TInAddr(PInAddr(HostEnt.h_addr_list^)^)));
addr.sin_addr.S_addr := inet_addr(Pansichar(IP));
if SOCKET_ERROR = connect(client, TSockAddr(addr), sizeof(addr)) then
begin
ShowMessage('连接失败!');
i := WSAGetLastError;
end
else
begin
StatusBar1.Panels[0].Text := IP + '已经连接服务端' + ',其端口号是' + inttostr(ntohs(addr.sin_port));
CloseHandle(CreateThread(nil, 0, @TalkeThread, nil, 0, ID));
end;
end;
end;
end;
procedure TMyClient.btn_sendClick(Sender: TObject);
var
len: integer;
buffer: array [0 .. 1024] of char;
begin
FillChar(buffer, sizeof(buffer), '');
StrPCopy(buffer, edt_send.Text);
send(client, buffer, length(buffer), 0);
Memo1.Lines.Add('我说:' + buffer);
end;
procedure TMyClient.Button1Click(Sender: TObject);
begin
closesocket(client);
WSACleanup();
bStop := false;
StatusBar1.Panels[0].Text := '关闭服务!';
end;
end.
2 无连接协议的套接字调用时序图
3 面向连接的应用程序流程图