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.
3 面向连接的应用程序流程图