1 基于TCP协议的客户机与服务器
2 基于UDP协议的客户机与服务器
1 基于TCP协议的客户机与服务器
1.1 问题
一个完整TCP通信过程需要依次经历三个阶段:
首先,客户机必须建立与服务器的连接,所谓虚电路。
然后,凭借已建立好的连接,通信双方相互交换数据。
最后,客户机与服务器双双终止连接,结束通信过程。
1.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:服务器
代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“bind”);
exit (EXIT_FAILURE);
}
if (listen (listenfd, 1024) == -1)
{
perror (“listen”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
int connfd = accept (listenfd, (struct sockaddr*)&addrcli, &addrlen);
if (connfd == -1)
{
perror (“accept”);
exit (EXIT_FAILURE);
}
printf (“服务器已接受来自%s:%hu客户机的连接请求\n”, inet_ntoa (addrcli.sin_addr),ntohs (addrcli.sin_port));
char buf[1024];
ssize_t rcvd = recv (connfd, buf, sizeof (buf), 0);
if (rcvd == -1)
{
perror (“recv”);
exit (EXIT_FAILURE);
}
if (rcvd == 0)
{
printf (“客户机已关闭连接\n”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“客户端说:%s\n”, buf);
printf (“服务器说:”);
gets (buf);
ssize_t sent = send (connfd, buf, strlen (buf) * sizeof (buf[0]), 0);
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
if (close (connfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
上述代码中,以下代码:
int listenfd = socket (AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
使用函数socket创建套接字。
上述代码中,以下代码:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
准备地址结构,使用sockaddr_in结构体类型。
上述代码中,以下代码:
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“bind”);
exit (EXIT_FAILURE);
}
使用函数bind将套接字对象和自己的地址结构绑定在一起。
上述代码中,以下代码:
if (listen (listenfd, 1024) == -1)
{
perror (“listen”);
exit (EXIT_FAILURE);
}
使用函数listen在指定套接字上启动对连接请求的侦听。该函数有两个参数,说明如下:
第一个参数为套接字描述符。
第二个参数为连接请求的最大值。
上述代码中,以下代码:
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
int connfd = accept (listenfd, (struct sockaddr*)&addrcli, &addrlen);
if (connfd == -1)
{
perror (“accept”);
exit (EXIT_FAILURE);
}
printf (“服务器已接受来自%s:%hu客户机的连接请求\n”, inet_ntoa (addrcli.sin_addr),ntohs (addrcli.sin_port));
使用函数accept在指定套接字上等待并接受连接请求。该函数有三个参数,说明如下:
第一个参数为套接字描述符。
第二个参数为输出连接请求发起者地址结构。
第三个参数为输入/输出,连接请求发起者地址结构长度(以字节为单位)。
上述代码中,以下代码:
char buf[1024];
ssize_t rcvd = recv (connfd, buf, sizeof (buf), 0);
if (rcvd == -1)
{
perror (“recv”);
exit (EXIT_FAILURE);
}
if (rcvd == 0)
{
printf (“客户机已关闭连接\n”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“客户端说:%s\n”, buf);
使用函数recv通过指定套接字接收数据,该函数有四个参数,说明如下:
第一个参数为套接字描述符。
第二个参数为应用程序接收缓冲区。
第三个参数为期望接收的字节数。
第四个参数为接收标志,一般取0,还可取以下值:
MSG_DONTWAIT - 以非阻塞方式接受数据。
MSG_OOB - 接收带外数据。
MSG_PEEK - 只查看可接收的数据,函数返回后数据依然留在接收缓冲区中。
MSG_WAITALL - 等待所有数据,即不接收到len字节的数据,函数就不返回。
上述代码中,以下代码:
printf ("服务器说:");
gets (buf);
ssize_t sent = send (connfd, buf, strlen (buf) * sizeof (buf[0]), 0);
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
使用函数send通过指定套接字发送数据,该函数有四个参数,说明如下:
第一个参数为套接字描述符。
第二个参数为应用程序发送缓冲区。
第三个参数为期望发送的字节数。
第四个参数为接收标志,一般取0,还可取以下值:
MSG_DONTWAIT - 以非阻塞方式发送数据。
MSG_OOB - 发送带外数据。
MSG_DONTROUTE - 不查路由表,直接在本地网络中寻找目的主机
上述代码中,以下代码:
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
if (close (connfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
使用函数close关闭处于打开状态的套接字描述符,该函数的参数为处于打开状态的套接字描述符。
步骤二:客户端
代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
if (connect (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“connect”);
exit (EXIT_FAILURE);
}
char buf[1024] = “你好,服务器”;
printf (“客户端说:%s\n”, buf);
ssize_t sent = send (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0);
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
ssize_t rcvd = recv (listenfd, buf, sizeof (buf), 0);
if (rcvd == -1)
{
perror (“recv”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“服务器说:%s\n”, buf);
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
上述代码中,以下代码:
int listenfd = socket (AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
使用函数socket创建套接字。
上述代码中,以下代码:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
准备地址结构,使用sockaddr_in结构体类型。
上述代码中,以下代码:
if (connect (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“connect”);
exit (EXIT_FAILURE);
}
使用函数connect将套接字对象和对方的地址结构连接在一起。
上述代码中,以下代码:
char buf[1024] = “你好,服务器”;
printf (“客户端说:%s\n”, buf);
ssize_t sent = send (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0);
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
使用函数send通过指定套接字发送数据。
上述代码中,以下代码:
ssize_t rcvd = recv (listenfd, buf, sizeof (buf), 0);
if (rcvd == -1)
{
perror (“recv”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“服务器说:%s\n”, buf);
使用函数recv通过指定套接字接收数据。
上述代码中,以下代码:
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
使用函数close关闭处于打开状态的套接字描述符,该函数的参数为处于打开状态的套接字描述符。
1.3 完整代码
本案例的完整代码如下所示:
服务器,代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“bind”);
exit (EXIT_FAILURE);
}
if (listen (listenfd, 1024) == -1)
{
perror (“listen”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
int connfd = accept (listenfd, (struct sockaddr*)&addrcli, &addrlen);
if (connfd == -1)
{
perror (“accept”);
exit (EXIT_FAILURE);
}
printf (“服务器已接受来自%s:%hu客户机的连接请求\n”, inet_ntoa (addrcli.sin_addr),ntohs (addrcli.sin_port));
char buf[1024];
ssize_t rcvd = recv (connfd, buf, sizeof (buf), 0);
if (rcvd == -1)
{
perror (“recv”);
exit (EXIT_FAILURE);
}
if (rcvd == 0)
{
printf (“客户机已关闭连接\n”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“客户端说:%s\n”, buf);
printf (“服务器说:”);
gets (buf);
ssize_t sent = send (connfd, buf, strlen (buf) * sizeof (buf[0]), 0);
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
if (close (connfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
客户端,代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
if (connect (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“connect”);
exit (EXIT_FAILURE);
}
char buf[1024] = “你好,服务器”;
printf (“客户端说:%s\n”, buf);
ssize_t sent = send (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0);
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
ssize_t rcvd = recv (listenfd, buf, sizeof (buf), 0);
if (rcvd == -1)
{
perror (“recv”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“服务器说:%s\n”, buf);
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
2 基于UDP协议的客户机与服务器
2.1 问题
UDP不提供客户机与服务器的连接。UDP的客户机与服务器不必存在长期关系。一个UDP的客户机在通过一个套接字向一个UDP服务器发送了一个数据报之后,马上可以通过同一个套接字向另一个UDP服务器发送另一个数据报。同样,一个UDP服务器也可以通过同一个套接字接收来自不同客户机的数据报
UDP不保证数据传输的可靠性和有序性。UDP的协议栈底层不提供诸如确认、超时重传、RTT估算以及序列号等机制。因此UDP数据报在网络传输的过程中,可能丢失,也可能重复,甚至重新排序。应用程序必须自己处理这些情况
2.2 步骤
实现此案例需要按照如下步骤进行。
步骤一:服务器
代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“bind”);
exit (EXIT_FAILURE);
}
char buf[1024];
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrcli, &addrlen);
if (rcvd == -1)
{
perror (“recvfrom”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“客户端说:%s\n”, buf);
printf (“服务器说:”);
gets (buf);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addrcli, sizeof (addrcli));
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
上述代码中,以下代码:
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
使用函数socket创建套接字。
上述代码中,以下代码:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
准备地址结构,使用sockaddr_in结构体类型。
上述代码中,以下代码:
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“bind”);
exit (EXIT_FAILURE);
}
使用函数bind将套接字对象和自己的地址结构绑定在一起。
上述代码中,以下代码:
char buf[1024];
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrcli, &addrlen);
if (rcvd == -1)
{
perror (“recvfrom”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“客户端说:%s\n”, buf);
使用函数recvfrom从指定的地址结构接收数据,该函数有六个参数,说明如下:
第一个参数为套接字描述符。
第二个参数为应用程序接收缓冲区。
第三个参数为期望接收的字节数。
第四个参数为接收标志,一般取0,还可取以下值:
MSG_DONTWAIT - 以非阻塞方式接受数据。
MSG_OOB - 接收带外数据。
MSG_PEEK - 只查看可接收的数据,函数返回后数据依然留在接收缓冲区中。
MSG_WAITALL - 等待所有数据,即不接收到len字节的数据,函数就不返回。
第五个参数为输出数据报发送者的地址结构,可置NULL。
第六个参数为输入src_addr参数所指向内存块的字节数,输出数据报发送者地址结构的字节数,可置NULL。
上述代码中,以下代码:
printf ("服务器说:");
gets (buf);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addrcli, sizeof (addrcli));
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
使用函数sendto从指定的地址结构发送数据,该函数有六个参数,说明如下:
第一个参数为套接字描述符。
第二个参数为应用程序发送缓冲区。
第三个参数为期望发送的字节数。
第四个参数为发送标志,一般取0,还可取以下值:
MSG_DONTWAIT - 以非阻塞方式接受数据。
MSG_OOB - 接收带外数据。
MSG_DONTROUTE - 不查路由表,直接在本地网络中寻找目的主机
第五个参数为数据报接收者的地址结构。
第六个参数为数据报接收者地址结构的字节数。
上述代码中,以下代码:
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
使用函数close关闭处于打开状态的套接字描述符,该函数的参数为处于打开状态的套接字描述符。
步骤二:客户端
代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
char buf[1024] = “你好,服务器”;
printf (“客户端说:%s\n”, buf);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addr, sizeof (addr));
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addrser = {};
socklen_t addrlen = sizeof (addrser);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrser, &addrlen);
if (rcvd == -1)
{
perror (“recvfrom”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“服务器说:%s\n”, buf);
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
上述代码中,以下代码:
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
使用函数socket创建套接字。
上述代码中,以下代码:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
准备地址结构,使用sockaddr_in结构体类型。
上述代码中,以下代码:
char buf[1024] = “你好,服务器”;
printf (“客户端说:%s\n”, buf);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addr, sizeof (addr));
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
使用函数sendto从指定的地址结构发送数据。
上述代码中,以下代码:
struct sockaddr_in addrser = {};
socklen_t addrlen = sizeof (addrser);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrser, &addrlen);
if (rcvd == -1)
{
perror (“recvfrom”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“服务器说:%s\n”, buf);
使用函数recvfrom从指定的地址结构接收数据。
上述代码中,以下代码:
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
使用函数close关闭处于打开状态的套接字描述符,该函数的参数为处于打开状态的套接字描述符。
2.3 完整代码
本案例的完整代码如下所示:
服务器,代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror (“bind”);
exit (EXIT_FAILURE);
}
char buf[1024];
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrcli, &addrlen);
if (rcvd == -1)
{
perror (“recvfrom”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“客户端说:%s\n”, buf);
printf (“服务器说:”);
gets (buf);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addrcli, sizeof (addrcli));
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}
客户端,代码如下所示:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror (“socket”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr(“127.0.0.1”);
char buf[1024] = “你好,服务器”;
printf (“客户端说:%s\n”, buf);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addr, sizeof (addr));
if (sent == -1)
{
perror (“send”);
exit (EXIT_FAILURE);
}
struct sockaddr_in addrser = {};
socklen_t addrlen = sizeof (addrser);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrser, &addrlen);
if (rcvd == -1)
{
perror (“recvfrom”);
exit (EXIT_FAILURE);
}
buf[rcvd] = ‘\0’;
printf (“服务器说:%s\n”, buf);
if (close (listenfd) == -1)
{
perror (“close”);
exit (EXIT_FAILURE);
}
return 0;
}