注:本文章是整合了自己的经验和网络资源完成的
对于socket编程而言,有两个概念,一个是ServerSocket,一个是socket。服务端和客户端之间通过socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的socket与之进行通信。这样就有两个socket了,客户端和服务端各一个。
TCP网络通信模型
Linux系统支持套接字接口。你可以通过与使用管道类似的方法来使用套接字,但套接字还包括了计算机网络中的通信。一台机器上的进程可以使用套接字和另外一台机器上的进程通信,这样就可以支持分布在网络中的客户/服务器系统。同一台机器上的进程之间也可以使用套接字进行通信。
此外,微软的Windows系统也通过可公开获取的Windows Sockets技术规范(简称WinSock)实现了套接字接口。Windows系统的套接字服务是由系统文件Winsock.dll来提供的。因此,Windows程序可以通过网络和Linux/UNIX计算机进行通信来实现客户/服务器系统,反之亦然。虽然WinSock的编程接口和UNIX套接字不尽相同,但它同样是以套接字为基础的。
首先,服务器应用程序用系统调用socket来创建一个套接字,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他进程共享。
接下来,服务器进程会给套接字起个名字。本地套接字的名字是Linux文件系统中的文件名,一般放在/tmp或/usr/tmp目录中。对于网络套接字,它的名字是与客户连接的特定网络有关的服务标识符(端口号或访问点)。这个标识符允许Linux将进入的针对特定端口号的连接转到正确的服务器进程。例如,Web服务器一般在80端口上创建一个套接字,这是一个专用于此目的的标识符。Web浏览器知道对于用户想要访问的Web站点,应该使用端口80来建立HTTP连接。
我们用系统调用bind来给套接字命名。然后服务器进程就开始等待客户连接到这个命名套接字。系统调用listen的作用是,创建一个队列并将其用于存放来自客户的进入连接。服务器通过系统调用accept来接受客户的连接。
服务器调用accept时,它会创建一个与原有的命名套接字不同的新套接字。这个新套接字只用于与这个特定的客户进行通信,而命名套接字则被保留下来继续处理来自其他客户的连接。如果服务器编写得当,它就可以充分利用多个连接带来的好处。Web服务器就会这么做以同时服务来自许多客户的页面请求。对一个简单的服务器来说,后续的客户将在监听队列中等待,直到服务器再次准备就绪。
基于套接字系统的客户端更加简单。客户首先调用socket创建一个未命名套接字,然后将服务器的命名套接字作为一个地址来调用connect与服务器建立连接。
一旦连接建立,我们就可以像使用底层的文件描述符那样用套接字来实现双向的数据通信。
实验 一个简单的本地客户
下面是一个非常简单的套接字客户程序的例子client1.c,它创建一个未命名的套接字,然后把它连接到服务器套接字server_socket。
(1)包含一些必要的头文件并设置变量
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<sys/un.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int sockfd;
int len;
struct sockaddr_un address;
int result;
char ch = 'A';
}
(2)为客户创建一个套接字
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
(3)根据服务器的情况给套接字命名
address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");
len = sizeof(address);
(4)将我们的套接字连接到服务器的套接字:
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1)
{
perror("oops:client1");
exit(1);
}
(5)现在就可以通过sockfd进行读写操作了:
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
总的client1.c
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<sys/un.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int sockfd;
int len;
struct sockaddr_un address;
int result;
char ch = 'A';
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");
len = sizeof(address);
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1)
{
perror("oops:client1");
exit(1);
}
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
实验 一个简单的本地服务器
下面是一个非常简单的服务器程序server1.c,它接受来自客户程序的连接。它首先创建一个服务器套接字,将它绑定到一个名字,然后创建一个监听队列,开始接受客户的连接。
(1)包含必要的头文件并设置变量
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<sys/un.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
(2)删除以前的套接字,为服务器创建一个未命名的套接字
unlink("server_socket");
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
(3)命名套接字
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
(4)创建一个连接队列,开始等待客户进行连接
listen(server_sockfd, 5);
while(1)
{
char ch;
printf("server waiting\n");
(5)接受一个连接
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, (socklen_t *)&client_len);
(6)对client_sockfd套接字上的客户进行读写操作
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
总的server1.c
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<sys/un.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;
unlink("server_socket");
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5);
while(1)
{
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, (socklen_t *)&client_len);
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
这个例子中的服务器程序一次只能为一个客户服务。它从客户那里读取一个字符,增加它的值,然后再把它写回去。在更加复杂的系统中,服务器需要为每个客户执行更多的处理工作,这种一次只为一个客户服务的做法就变得不可接受了,因为其他客户只有等到服务器结束上一个客户的处理任务后才能处理它的连接。我们将在后面看到几个允许同时处理多个连接的解决方案。
运行服务器程序时,它创建一个套接字并开始等待客户的连接。
记住:用完一个套接字后,就应该把它删除掉,即使是在程序因接收到一个信号而异常终止的情况下也应该这么做。这可以避免文件系统因充斥着无用的文件而变得混乱。
访问权限前面的字母s和这一行末尾的等号=表示该设备的类型是“套接字”。套接字的创建过程与普通文件一样,它的访问权限会被当前的掩码值所修改。如果使用ps命令,你可以看到服务器正运行在后台。它目前处于休眠状态(STAT栏显示的是S),因此它没有消耗CPU资源
现在运行客户程序,你就可以成功地连接到服务器了。因为服务器套接字已经存在,所以你可以连接到它并与服务器进行通信。
socket文件描述符
socket翻译成中文是插座、插槽的意思,而在网络编程中,其被翻译为“套接字”。Linux环境下,我们经常说“一切皆文件”。因此套接字也被视为一种文件描述符。首先,来看看如何使用socket系统调用创建一个套接字,代码如下:
套接字的特性由3个属性确定,它们是:域(domain)、类型(type)和协议(protocol)。套接字还用地址作为它的名字。地址的格式随域(又被称为协议族,protocol family)的不同而不同。每个协议族又可以使用一个或多个地址族来定义地址格式。
其中的参数解释如下
·domain:用于指示协议族名字,如AF_INET为IPv4。
最常用的套接字域是AF_UNIX和AF_INET,前者用于通过UNIX和Linux文件系统实现的本地套接字,后者用于UNIX网络套接字。AF_INET套接字可以用于通过包括因特网在内的TCP/IP网络进行通信的程序。微软Windows系统的Winsock接口也提供了对这个套接字域的访问功能。
第一个例子中的域是UNIX文件系统域AF_UNIX,即使是一台还未联网的计算机上的套接字也可以使用这个域。这个域的底层协议就是文件输入/输出,而它的地址就是文件名。我们的服务器套接字的地址是server_socket,当我们运行服务器程序时,就可以在当前目录下看到这个地址。
·type:用于指示类型,如基于流通信的SOCK_STREAM
socket函数的参数type指定用于新套接字的通信特性。它的取值包括SOCK_STREAM和SOCK_DGRAM。
SOCK_STREAM是一个有序、可靠、面向连接的双向字节流。对AF_INET域套接字来说,它默认是通过一个TCP连接来提供这一特性的,TCP连接在两个流套接字端点之间建立。数据可以通过套接字连接进行双向传递。TCP协议所提供的机制可以用于分片和重组长消息,并且可以重传可能在网络中丢失的数据。
SOCK_DGRAM是数据报服务。我们可以用它来发送最大长度固定(通常比较小)的消息,但消息是否会被正确传递或消息是否不会乱序到达并没有保证。对于AF_INET域套接字来说,这种类型的通信是由UDP数据报来提供的。
因特网协议提供了两种通信机制:流(stream)和数据报(datagram)。它们有着截然不同的服务层次。
流套接字流套接字(在某些方面类似于标准的输入/输出流)提供的是一个有序、可靠、双向字节流的连接。
因此,发送的数据可以确保不会丢失、复制或乱序到达,并且在这一过程中发生的错误也不会显示出来。大的消息将被分片、传输、再重组。这很像一个文件流,它接收大量的数据,然后以小数据块的形式将它们写入底层磁盘。流套接字的行为是可预见的。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。它们也是AF_UNIX域中常用的套接字类型。
数据报套接字
与流套接字相反,由类型SOCK_DGRAM指定的数据报套接字不建立和维持一个连接。它对可以发送的数据报的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失、复制或乱序到达。
数据报套接字是在AF_INET域中通过UDP/IP连接实现的,它提供的是一种无序的不可靠服务(UDP代表的是用户数据报协议)。但从资源的角度来看,相对来说它们开销比较小,因为不需要维持网络连接。而且因为无需花费时间来建立连接,所以它们的速度也很快。
·protocol:用于指示对于这种socket的具体协议类型。
一般情况下,使用前两个参数限定后,只会存在一种协议类型对应该情况。这时,可以将protocol设置为0。但是在某些情况下,会存在多个协议类型,这时就必须指定具体的协议类型。
成功创建socket后,会返回一个文件描述符。失败时,该接口返回-1
套接字地址
每个套接字域都有其自己的地址格式。对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结构定义在头文件sys/un.h中。
在AF_INET域中,套接字地址由结构sockaddr_in来指定,该结构定义在头文件netinet/in.h中,它至少包含以下几个成员:
套接字与管道区别
套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分开来。套接字机制可以实现将多个客户连接到一个服务器。
命名套接字
要想让通过socket调用创建的套接字可以被其他进程使用,服务器程序就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名,正如你在server1例子中所看到的。AF_INET套接字就会关联到一个IP端口号。
bind调用
bind系统调用把参数address中的地址分配给与文件描述符socket关联的未命名套接字。地址结构的长度由参数address_len传递。
地址的长度和格式取决于地址族。bind调用需要将一个特定的地址结构指针转换为指向通用地址类型(struct sockaddr *)。
其中的参数解释如下
·sockfd:表示要绑定地址的套接字描述符
·addr:表示绑定到套接字的地址,是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针
·addrlen:表示绑定的地址长度
返回值0表示成功,-1则表示错误。
创建套接字队列
为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。它用listen系统调用来完成这一工作。
listen调用
Linux系统可能会对队列中可以容纳的未处理连接的最大数目做出限制。为了遵守这个最大值限制,listen函数将队列长度设置为backlog参数的值。在套接字队列中,等待处理的进入连接的个数最多不能超过这个数字。再往后的连接将被拒绝,导致客户的连接请求失败。listen函数提供的这种机制允许当服务器程序正忙于处理前一个客户请求的时候,将后续的客户连接放入队列等待处理。backlog参数常用的值是5。
listen函数在成功时返回0,失败时返回-1。错误代码包括EBADF、EINVAL和ENOTSOCK,其含义与上面bind系统调用中说明的一样。
接受连接
一旦服务器程序创建并命名了套接字之后,它就可以通过accept系统调用来等待客户建立对该套接字的连接。
accept调用
accept系统调用只有当有客户程序试图连接到由socket参数指定的套接字上时才返回。这里的客户是指,在套接字队列中排在第一个的未处理连接。accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。
套接字必须事先由bind调用命名,并且由listen调用给它分配一个连接队列。连接客户的地址将被放入address参数指向的sockaddr结构中。如果我们不关心客户的地址,也可以将address参数指定为空指针。
参数address_len指定客户结构的长度。如果客户地址的长度超过这个值,它将被截断。所以在调用accept之前,address_len必须被设置为预期的地址长度。当这个调用返回时,address_len将被设置为连接客户地址结构的实际长度。
关于返回值,若执行成功,则返回一个非负的文件描述符;若失败则返回-1。
注:若不关心对端地址信息,则可以将addr和addrlen设置为NULL。
如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)直到有客户建立连接为止。我们可以通过对套接字文件描述符设置O_NONBLOCK标志来改变这一行为,使用的函数是fcntl,如下所示:
请求连接
客户程序通过在一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器。它们通过connect调用来完成这一工作
connect调用
参数socket指定的套接字将连接到参数address指定的服务器套接字,address指向的结构的长度由参数address_len指定。参数socket指定的套接字必须是通过socket调用获得的一个有效的文件描述符。
成功时,connect调用返回0,失败时返回-1。
关闭套接字
你可以通过调用close函数来终止服务器和客户上的套接字连接,就如同对底层文件描述符进行关闭一样。你应该总是在连接的两端都关闭套接字。对于服务器来说,应该在read调用返回0时关闭套接字,但如果套接字是一个面向连接类型的,并且设置了SOCK_LINGER选项,close调用会在该套接字还有未传输数据时阻塞。
TCP程序设计
tcp_server.c
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#define portnumber 3333
int main(int argc, char *argv[])
{
int sockfd, new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
int nbytes;
char buffer[1024];
//建立sockfd描述符
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
exit(1);
}
//命名套接字
//服务器端填充sockaddr结构
bzero(&server_addr, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//其中INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
//如果指定ip地址为通配地址(INADDR_ANY),那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报时才选择一个本地IP地址。
server_addr.sin_port = htons(portnumber);
//htons 是把你机器上的整数转换成“网络字节序”, 网络字节序是 big-endian,也就是整数的高位字节存放在内存的低地址处
//捆绑sockfd描述符到IP地址上
if(bind(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
exit(1);
}
//监听
if(listen(sockfd, 5) == -1)
{
fprintf(stderr, "Listen error:%s\n\a", strerror(errno));
exit(1);
}
while(1)
{
//服务器阻塞,等待客户机
sin_size = sizeof(struct sockaddr_in);
if((new_fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size)) == -1)
{
fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
exit(1);
}
//把网络格式的ip地址转为字符串形式
//char* inet_ntoa(struct in_ addr inaddr);
fprintf(stderr, "Server get connection from %s\n", inet_ntoa(client_addr.sin_addr));
if((nbytes = read(new_fd, buffer, 1024)) == -1)
{
fprintf(stderr, "Read Error:%s\n", strerror(errno));
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received %s\n", buffer);
//通讯结束,循环下一个
close(new_fd);
}
//关闭套接字
close(sockfd);
exit(0);
}
htons的功能:将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
tcp_client.c
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#define portnumber 3333
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
if(argc != 2)
{
fprintf(stderr, "Usage:%s hostname \a\n", argv[0]);
exit(1);
}
//通过域名获取IP地址
if((host = gethostbyname(argv[1])) == NULL)
{
fprintf(stderr, "Gethostname error\n");
exit(1);
}
//客户机建立sockfd描述符
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
exit(1);
}
//客户机填充服务端资料
bzero(&server_addr, sizeof(server_addr));
//配置信息
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnumber);
server_addr.sin_addr = *((struct in_addr*)host->h_addr);
//请求连接
if(connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
exit(1);
}
printf("Please input char:\n");
//发送数据
fgets(buffer, 1024, stdin);
write(sockfd, buffer, strlen(buffer));
//结束通讯
close(sockfd);
exit(0);
}
gethostbyname函数
gethostbyname函数返回hostent结构指针,hostent结构中的h_list字段本身也是一个指针,它指向一个IP地址的指针列表,当主机名对应多个IP地址的时候,这个指针列表中就存在多个表项,最后一个表项总是NULL,用来指示列表的结束。真正的IP地址数据的存放位置由指针列表中的多个指针指出。
如果转换失败,函数返回0,成功的话函数的返回值也是一个指向hostent结构的指针,结构的第一个字段h_name指向转换后的主机名。
回路网络
我们将在局域网中运行我们的客户和服务器,但网络套接字不仅可用于局域网,任何带有因特网连接(即使是一个调制解调器拨号连接)的机器都可以使用网络套接字来彼此通信。甚至可以在一台UNIX单机上运行基于网络的程序,因为UNIX计算机通常会配置了一个只包含它自身的回路(loopback)网络。出于演示的目的,我们将使用这个回路网络。回路网络对调试网络应用程序也很有用,因为它排除了任何外部网络问题。
回路网络中只包含一台计算机,传统上它被称为localhost,它有一个标准的IP地址127.0.0.1。这就是本地主机。你可以在网络主机文件/etc/hosts中找到它的地址,在该文件中还列出了在共享网络中的其他主机的名字和对应的地址。
改版
tcp_client.c
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#define portnumber 3346
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
char msg[1024];
struct sockaddr_in server_addr;
struct hostent *host;
int nbytes;
if(argc != 2)
{
fprintf(stderr, "Usage:%s hostname \a\n", argv[0]);
exit(1);
}
if((host = gethostbyname(argv[1])) == NULL)
{
fprintf(stderr, "Gethostname error\n");
exit(1);
}
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
exit(1);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(portnumber);
server_addr.sin_addr = *((struct in_addr*)host->h_addr);
if(connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
exit(1);
}
while(1)
{
if(fork() == 0)
{
while(1)
{
printf("Please input char:\n");
memset(msg, 0, sizeof(msg));
fgets(buffer, 1024, stdin);
write(sockfd, msg, strlen(msg));
}
}
while(1)
{
memset(buffer, 0, sizeof(buffer));
if((nbytes = read(sockfd, buffer, 1024)) == -1)
{
fprintf(stderr, "Read Error:%s\n", strerror(errno));
exit(1);
}
buffer[nbytes] = '\0';
printf("Client received:%s\n", buffer);
}
}
}
tcp_server.c
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#define portnumber 3346
int main(int argc, char *argv[])
{
int sockfd, new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char msg[1024];
int sin_size;
int nbytes;
pid_t child;
char buffer[1024];
unlink("sockfd");
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
exit(1);
}
bzero(&server_addr, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(portnumber);
if(bind(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "Bind error:%s\n\a", strerror(errno));
exit(1);
}
if(listen(sockfd, 5) == -1)
{
fprintf(stderr, "Listen error:%s\n\a", strerror(errno));
exit(1);
}
while(1)
{
sin_size = sizeof(struct sockaddr_in);
if((new_fd = accept(sockfd, (struct sockaddr *)(&client_addr), &sin_size)) == -1)
{
fprintf(stderr, "Accept error:%s\n\a", strerror(errno));
exit(1);
}
fprintf(stderr, "Server get connection from %s\n", inet_ntoa(client_addr.sin_addr));
if(fork() == 0)
{
if(fork() == 0)
{
while(1)
{
memset(msg, 0, sizeof(msg));
printf("intput:");
fgets(msg, 1024, stdin);
write(new_fd, msg, strlen(msg));
}
}
while(1)
{
memset(buffer, 0, sizeof(buffer));
if((nbytes = read(new_fd, buffer, 1024)) == -1)
{
fprintf(stderr, "Read Error:%s\n", strerror(errno));
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received: %s\n", buffer);
}
}
}
}