网络编程套接字之三TCP
Posted 快到锅里来呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程套接字之三TCP相关的知识,希望对你有一定的参考价值。
目录
1. ServerSocket API(给服务器端使用的类)
2. Socket API(既给服务器使用,也给客户端使用)
TCP流套接字编程
1. ServerSocket API(给服务器端使用的类)
ServerSocket 是创建TCP服务端Socket的API。
构造方法
方法签名 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法
方法签名 | 说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于改Socket建立与客户端的连接,否则阻塞等待(接受客户端的连接) |
void close() | 关闭此套接字 |
2. Socket API(既给服务器使用,也给客户端使用)
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的
构造方法
方法签名 | 说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接(尝试和指定的服务器建立连接) |
方法
方法签名 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址(返回套接字获取到对方的IP地址和端口) |
InputStream getInputStream() | 返回此套接字的输入流(通过Socket可以获取到两个流对象,分别用来读和写) |
OutputStream getOutputStream() | 返回此套接字的输出流 |
3. 写TCP回显—服务器
1.先写一个ServerSocket对象
private ServerSocket listenSocket = null;
2.下面写Tcp服务器构造方法
public TcpEchoServer(int port) throws IOException listenSocket = new ServerSocket(port);
3.写一个启动服务器的方法start(),在start中写上while循环来执行
a)调用accept来接收客户端的连接
b)再处理这个连接,这里写一个processConnection()方法来处理
public void start() throws IOException System.out.println("服务器启动!"); while(true) //1. 先调用 accept 来接受客户端的连接 Socket clientSocket = listenSocket.accept(); //2. 再处理这个连接 processConnection(clientSocket);
4.下面来写这个processConnection()方法,处理连接客户端连接
方法中写try(这里写上InputStream(读)和OutPutStream(写)对象,写在try中帮助资源回收) 这里写上写具体处理逻辑步骤
注意最后必须要写上finally来close关闭clientSocket
private void processConnection(Socket clientSocket) throws IOException System.out.printf("[%s:%d] 客户端上线!\\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); //接下来处理客户端请求 try(InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) while(true) finally //这里要关闭socket,是因为 //socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的 clientSocket.close();
a)读取请求并解析
Scanner scanner = new Scanner(inputStream); if(!scanner.hasNext()) //读完了,连接可以断开了 System.out.printf("[%s:%d] 客户端下线!\\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); break; String request = scanner.next();
b)根据请求计算响应(这里写一个process方法)
String response = process(request);
private String process(String request) return request;
c)响应写回到客户端
PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(response); //刷新缓冲区确保数据确实通过网卡发送出去了 printWriter.flush();
d)将发送的信息显示到服务器界面上
System.out.printf("[%s:%d] req: %s; resp: %s\\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request,response);
5.最后再写上mian方法来执行服务器
public static void main(String[] args) throws IOException TcpEchoServer tcpEchoServer = new TcpEchoServer(9090); tcpEchoServer.start();
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Semaphore;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:05
*/
public class TcpEchoServer
//代码中会设计到多个 socket 对象,使用不同的名字来区分
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException
listenSocket = new ServerSocket(port);
public void start() throws IOException
System.out.println("服务器启动!");
while(true)
//1. 先调用 accept 来接受客户端的连接
Socket clientSocket = listenSocket.accept();
//2. 再处理这个连接
processConnection(clientSocket);
private void processConnection(Socket clientSocket) throws IOException
System.out.printf("[%s:%d] 客户端上线!\\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下来处理客户端请求
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream())
while(true)
//1.读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext())
//读完了,连接可以断开了
System.out.printf("[%s:%d] 客户端下线!\\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.响应写回到客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区确保数据确实通过网卡发送出去了
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort(),
request,response);
catch (IOException e)
e.printStackTrace();
finally
clientSocket.close();
private String process(String request)
return request;
public static void main(String[] args) throws IOException
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
下面思考为啥代码最后finally要执行clientSocket的close,而前面的listenSocket以及UDP程序中的socekt为啥就没close?
但是上面的代码有个问题,只能处理一个客户端的请求,(本质上第二个客户端的消息是发出去了,但服务器此时还在执行第一个客户端的请求,只要从第一个客户端这里出来,服务器就会立刻执行第二个客户端的消息)
我们希望的是既能够快速重复的调用到accept(也就是连接多个客户端),又能够循环的处理客户端的请求。所以就需要使用到多线程了
那么为什么前面UDP就不需要考虑这个问题,而TCP需要考虑
UDP是无连接,客户端直接发消息就行(不必专注于处理某一个客户端)
TCP建立连接之后,要处理客户端的多次请求,才导致无法快速的调用到accept(长连接)(主要原因)
如果TCP每个连接只处理一个客户端的请求,也能够保证快速调用到accept(短连接)
下面使用多线程,给每个客户端连上来的都分配一个新的线程负责处理请求
但是直接这样使用多线程,如果循环多次,对应就会创建很多线程,等线程执行完,又会消毁很多的线程,所以更好的方法就是使用线程池
4. 使用线程池后的TCP服务器代码(最终)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:05
*/
public class TcpEchoServer
//代码中会设计到多个 socket 对象,使用不同的名字来区分
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException
listenSocket = new ServerSocket(port);
public void start() throws IOException
System.out.println("服务器启动!");
ExecutorService service = Executors.newCachedThreadPool();
while(true)
//1. 先调用 accept 来接受客户端的连接
Socket clientSocket = listenSocket.accept();
//2. 再处理这个连接,这里应该要使用多线程,每个客户端连上来都分配一个新的线程负责处理
service.submit(new Runnable()
@Override
public void run()
try
processConnection(clientSocket);
catch (IOException e)
e.printStackTrace();
);
private void processConnection(Socket clientSocket) throws IOException
System.out.printf("[%s:%d] 客户端上线!\\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下来处理客户端请求
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream())
while(true)
//1.读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext())
//读完了,连接可以断开了
System.out.printf("[%s:%d] 客户端下线!\\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.响应写回到客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区确保数据确实通过网卡发送出去了
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort(),
request,response);
catch (IOException e)
e.printStackTrace();
finally
//这里要关闭socket,是因为
//socket也是一个文件,一个进程能够同时打开的文件个数有上限(PCB文件描述符表,不是无限的
clientSocket.close();
private String process(String request)
return request;
public static void main(String[] args) throws IOException
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
5. 写回显-客户端
1. 先写一个Socket对象,客户端用Socket来建立连接
private Socket socket = null;
2.下面写Tcp客户端构造(和Udp区别较大,有连接和无连接的区别)
public TcpEchoClient(String serverIP, int serverPort) throws IOException //和服务器建立连接,就需要知道服务器在哪 socket = new Socket(serverIP,serverPort);
3.写客户端执行方法start(),给start里面放try,try中执行的就是while,来让客户端循环输入,还要在try后面括号中写上InputStream(读)和OutputStream(写)的对象(写在括号中中try会自动帮助,关掉资源)
public void start() throws IOException Scanner scan = new Scanner(System.in); try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) while(true)
while中写 a)从控制台读取数据,构造一个请求
System.out.println("-> "); String request = scan.next();
b)发送请求给服务器(PrintWriter)
PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(request); //这个 flush 不要忘记,否则可能导致请求没有真发出去 printWriter.flush();
c)从服务器读取响应
Scanner respScanner = new Scanner(inputStream); String response = respScanner.next();
d)把响应显示到界面上
System.out.println(response);
4.最后再写上mian方法来执行客户端
public static void main(String[] args) throws IOException TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090); tcpEchoClient.start();
Udp和Tcp构造的区别,有连接和无连接
6. TCP回显—客户端代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:06
*/
public class TcpEchoClient
//客户端需要使用这个 Socket 对象来建立连接
private Socket socket = null;
public TcpEchoClient(String serverIP, int serverPort) throws IOException
//和服务器建立连接,就需要知道服务器在哪
socket = new Socket(serverIP,serverPort);
public void start() throws IOException
Scanner scan = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream())
while(true)
//1.从控制台读取数据,构造成一个请求
System.out.println("-> ");
String request = scan.next();
//2.发送请求给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
//这个 flush 不要忘记,否则可能导致请求没有真发出去
printWriter.flush();
//3.从服务器读取响应
Scanner respScanner = new Scanner(inputStream);
String response = respScanner.next();
//4.把响应显示到界面上
System.out.println(response);
public static void main(String[] args) throws IOException
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
7. 运行回显服务器和客户端
TCP/IP网络编程系列之三(初级)
TCP/IP网络编程系列之三-地址族与数据序列
分配给套接字的IP地址和端口
IP是Internet Protocol (网络协议)的简写,是为首发网络数据而分配给计算机的值。端口号并非赋予计算机值,而是为了区分程序中创建的套接字而分配给套接字的序号。
网络地址
网络地址分为IPV4和IPV6,分别你别为4个字节地址簇和6个字节地址簇。IPV4标准的4个字节的地址分为网络地址和主机地址,且分为A、B、C、D、E 等类型。一般很少用到E类型。如下图所示:net-id指网络ID,host-id指主机ID
说明:
A类地址的首字节范围是:0-127
B类地址的首字节范围是:128-191
C类地址的首字节范围是:192-223
或者也可以这样分
A类地址的首位是以0开头
B类地址的前两位是以10开头
C类地址的前三位是以110开头
概述
网络ID是为区分网络而设置的一部分IP地址。比如你向www.baidu.com公司传输数据,该公司内部构建了局域网,把所有的计算机连接起来。因此,首相向baidu.com网络传输数据,也就是说,并非一开始就浏览所有4字节的IP地址,进而找到目标主机;而是仅浏览4字节IP地址的网络地址,先把数据传到baidu.com的网络。baidu.com网络接收到数据之后,浏览传输数据的主机地址并将数据传输给目标地址。一般的网络都会有路由器和交换机,所以实际上是向路由器或交换机传递数据,由接收数据的路由器根据数据中的主机地址向目标主机传送数据。
用于区分套接字的端口号
IP用于区分计算机,只要有IP地址就能想目标主机传输数据,但是仅凭这些数据无法传输给最终的应用程序。假设在欣赏音乐的同时在听音乐或者上网浏览网页,这时至少需要1个接受视频数据的套接字和1个接受网页信息的套接字。但是如何区分它们呢,也就是说传输到计算机的网络数据是发送给视频播放器还是音乐播放器?假设我们开发了迅雷等应用程序,该程序用块单位分割一个文件,从多台计算机接受数据。那如何区分这些套接字呢?
计算机中一般都会有NIC(NetWork Interface Card,网络接口卡)数据传输设备。通过NIC向计算机内部传输数据时会用到IP。OS负责把传递到内部的数据适当的分配给套接字,这时就要利用端口号。通过NIC接受的数据内有端口号,操作系统正是参考此端口号把数据传输给相应端口的套接字。端口号就是在同一操作系统内为区分不同套接字而设置的,因此无法将一个端口号分配给不同的套接字。并且,端口号由16位构成,可分配的端口号的范围是0-65535,0-1023是知名端口号(Well-Know PORT),一般分配给特定应用程序,所以应当分配此范围之外的值。端口号是不能重复,但TCP套接字和UDP套接字不会公用端口号,所以允许重复。比如:某TCP套接字使用9190端口号,则其他TCP无法就无法使用端口号,但是UDP套接字就可以使用。
总之,数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最总的目的应用程序(应用程序套接字)。
地址信息的表示
应用程序中使用的IP地址和端口号以结构体的形式给出了定义,我们主要以IPV4为中心。
struct sockaddr_in { sa_family_t sin_family;//地址族 uint16_t sin_port;//16位TCP/UDP端口号 struct in_addr sin_addr;//32位IP地址 char sin_zero[8];//不使用 }; 该结构体中的 in_addr用来存放32位的IP地址,定义如下 struct in_addr { In_addr_t s_addr;//32位IP地址 };
uint16_t in_addr_t等类型可以参考POSIX,我这边简单说一下
数据类型 | 数据类型说明 | 声明的头文件 |
int8_t | signed 8-bit int | sys/types.h |
uint8_t | unsigned 8-bit int | sys/types.h |
int16_t | signed 16-bit int | sys/types.h |
uint16_t | unsigned 16-bit int | sys/types.h |
int32_t | signed 32-bit int |
sys/types.h |
这主要是考虑到扩展性,如果使用int32_t类型的数据,就能保证在任何时候都占用4字节,即使将来用64位的来存储也是一样。
结构体sockaddr_in的成员分析
- 成员sin_family
每种协议族适用的地址族不同,比如IPV4使用4个字节地址族,IPV6使用16字节地址簇
地址簇 含义
AF_INET IPV4网络协议中使用的地址族
AF_INET6 IPV6网络协议中使用的地址族
AF_LOCAL 本地通信中采用的UNIX协议的地址族
- 成员sin_port
该成员保存16位端口号,具体在下面讲解。
- sin_addr
保存32位的IP地址信息,且以网络字节序保存,结构体in_addr声明为uint32_t,一次只需要保存32位整数类型即可。
- sin_zero
无特殊含义,只是未使用结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员,必须填充为0,否则无法得到想要的结果。之前在服务端bind函数的时候,
struct sockaddr_in serv_addr;
if(bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr)==-1),其中第二个参数,是由sockaddr_in结构体转化而来的,并且希望得到sockaddr结构体变量地址,包括地址簇、端口号、IP地址等。但是我们直接向sockaddr结构体填充这些信息会带来麻烦。
struct sockaddr
{
sa_family_t sin_family;//地址族
char sa_data[14];//地址信息
};
sa_data保存地址信息,包括IP地址和端口号,剩余部分应填充为0,这也是bind函数的要求。这对于包含地址信息来讲非常麻烦,从而有了新的结构体sockaddr_in。
网络字节序和地址转换
-
字节序和网络字节序
cpu向内存保存数据有两种,这意味着cpu解析数据的方式也是两种分别为大端序和小端序。
- 大端序(Big Endian):高位字节存放到低位地址。
- 小端序(Little Endian):高位字节存放到高位地址。
比如0x00000001
大端序:内存低比特位 00000000 00000000 00000000 00000001 内存高比特位
小端序:内存低比特位 10000000 00000000 00000000 00000000 内存高比特位
还可以如下图表示:
所以出现了一个问题如果两台计算机的cpu的数据保存方式不同,但是他们是如何传送数据的呢?如何进行网络传输的呢?所以就规定了一个标准,在通过网络传输的过程中统一按照大端序。即先把数据数组转化为大端序格式然后进行传输。因此,所有计算机接受数据时应识别该数据的字节格式,小端序系统传输数据时应该转化为大端字节序的排列方式。
-
字节序转换(Endian Conversions)
所以我们懂应该知道为何在填充sockadr_in结构提前将数据转化为网络字节序。转换字节的函数:
unsigned short htons (unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htons (unsigned long);
unsigned long ntohs (unsigned long);
通过函数名可以知道他的功能,只要了解一下细节。htons中的h代表主机(host)字节序,n代表网络字节序。s指的是short,l指的是long(linux 中long类型占用4个字节),可以解释为"把short类型数据从主机字节序转化为网络字节序"。所以ntohs也就知道了吧。我们通过例子来看一下效果:
运行结果之后会看到如下图结果:
这就是小端序cpu运行结果,如果在大端值中是不会发生变化的。Intel和AMD系列的cpu都采用小端序标准。数据在传输过程中需要经过转换吗?实际上没有必要,这个过程是自动的。除了想sockaddr_in结构体变量填充数据外,其他情况不需要考虑字节序问题。
网络地址的初始化与分配
sockaddr_in 中保存地址信息的成员为32位整数型。因此,为了分配IP地址将其转化为32位整数型数据。对我们而言并非易事。对于IP地址的的表示,我们熟悉的是点分十进制,而非整型数据表示法。幸运的是,有个函数可以帮我们将字符串形式的IP地址转化为32位的整型数据。此函数在转换类型的同时进行网络字节序转换。
#include <arpa/inet.h> in_addr_t inet_addr(const char* string) 成功时返回32位大端序整型数据,失败时返回INADDR_NONE
下面是测试代码:
运行结果如下图所示:从运行结果可以看出,inet_addr函数不仅可以把ip地址转换为32位整数,而且还可以检测无效的ip地址。并且输出的确实是网络字节序。还有一个函数与inet_addr函数功能完全相同,只不过该函数利用了in_addr结构体,且使用频率更高。
#include <arpa/inet.h> int inet_aton(const char *string,struct in_addr *addr) 成功时返回1,失败时返回0;
实际编程中若要调用inet_addr函数,需要将转化后的IP地址信息代入sockaddr_in结构体中声明的in_addr结构体变量。而inet_aton函数不需要此过程。原因在于,若传递in_addr结构体变量地址值,函数会自动把结果填入该结构体变量。ok,下面再讲解一个把网络字节序整数型IP地址转换成我们熟悉的字符串形式。
#include <arpa/inet> char *inet_ntoa(struct in_addr adr); 成功时返回转换的字符串地址,失败时返回-1。
但在调用时小心,返回值是char类型的指针。返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完函数后,应该立即将字符串信息复制到其他内存空间。因为在此调用该函数,则有可能覆盖之前保存的字符串信息。总之,再次调用该函数前返回的字符串地址值是有效的。如要长期保存,则应将字符串复制到其他内存空间。示例:
运行结果如下:
下面我把之前的代码完全重新组合一下。
服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void ErrorMessage(char *message);
int main(int argc,char *argv[])
{
int serv_sock;
int client_sock;
struct sockaddr_in serv_addr;
struct sockaddr_in client_addr;
char *serverIP= "127.0.0.1";
char *servPort = "9190";
char message[]="Hi,TCPIP";
socklen_t clnt_addr_size;
serv_sock = socket(PF_INET,SOCK_STREAM,0);
if(serv_sock==-1)
{
ErrorMessage("Sock Error!");
}
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(serverIP);
serv_addr.sin_port = htons(atoi(servPort));
if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
{
ErrorMessage("Bind() Error");
}
if(listen(serv_sock,5)==-1)
{
ErrorMessage("listen() error");
}
clnt_addr_size = sizeof(client_addr);
client_sock = accept(serv_sock,(struct sockaddr*)&client_addr,&clnt_addr_size);
if(client_sock==-1)
{
ErrorMessage("accept() error");
}
write(client_sock,message,sizeof(message));
close(client_sock);
close(serv_sock);
return 0;
}
void ErrorMessage(char *message)
{
fputs(message,stderr);
fputc(\'\\n\',stderr);
exit(1);
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void ErrorMessage(char *message);
int main(int argc,char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
char *serv_port = "9190";
int str_len;
sock = socket(PF_INET,SOCK_STREAM,0);
if(sock==-1)
{
ErrorMessage("socket() error");
}
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(serv_port));
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
{
ErrorMessage("connect() error");
}
str_len = read(sock,message,sizeof(message)-1);
if(str_len==-1)
{
ErrorMessage("read() error");
}
printf("Message from server:%s\\n",message);
close(sock);
return 0;
}
void ErrorMessage(char *message)
{
fputs(message,stderr);
fputc(\'\\n\',stderr);
exit(1);
}
运行结果如下图所示:
以上是关于网络编程套接字之三TCP的主要内容,如果未能解决你的问题,请参考以下文章