网络编程_pthread_fork
前言
- TCP(传输控制协议)
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
- 套接字
套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。
一、套接字Socket相关函数
1.socket函数
注:本身并没有IP和端口号的具体数值
函数原型:
int socket(int family,int type,int protpcol)
- 参数解释
- domain 网络程序所在的主机采用的通信协议族
- type 网络程序所采用的通信协议
- protpcol 如果指定了type,可以直接使用0
- 返回值
成功 自然数>=0
错误 -1
2.bind函数
该函数为套接字描述符合分配一个本地IP地址和一个端口号,将IP地址和端口号与套接字描述符绑定在一起。
- 函数原型
int bind(int scokfd,struct sockaddr *myaddr,int addrlen);
- 参数解释
- sockfd 由socket调用返回的文件描述符
- addrlen sockaddr结构的长度
- myaddr 一个指向sockaddr的指针,在中有sockaddr的定义
- 返回值
成功 0
出错 -1
在网络编程中有两个很重要的数据类型
struct sockaddr
struct sockaddr_in
这两个数据类型都是用来存放socket信息的。
由于系统的兼容性,我们一般用struct sockaddr_in来代替。
如下是结构体sockaddr的定义
struct sockaddr{
unsigned short sa_family
char sa_data[14]
}
sa_family协议类型族AF_XXX
sa_data[14]IP+port
14字节协议地址包含该socket的IP地址和端口号
如下是结构体sockaddr_in的定义
struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
我们主要使用Internet因此
sin_family协议类型族一般为AF_INET,
sin_addr设置为INADDR_ANY表示可以和任何主机通信,
sin_port是我们要监听的端口,
sin_zero[8]是用来填充的,填充0以保持与sockaddr结构的长度相同。
3.listen函数
listen函数应用于TCP连接的服务程序,它的作用是设置socket套接字允许等待来自客户端的连接请求。
- 函数原型
int listen(int sockfd,int backlog);
- 参数解释
- sockfd 由socket调用返回的文件描述符
- backing 设置请求排队的最大长度,当有个多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
- 返回值
成功 0
失败 -1
accept函数
accept调用后,服务器程序会一直处于阻塞状态一直等待来自客户端的连接请求。
- 函数原型
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
- 参数解释
- sockfd 由socket调用返回的文件描述符
- addr/addrlen 用来给客户端程序填写的,服务器端只要传递指针就可以
- 返回值
收到请求:套接字描述符(>=0)
失败: -1
connect函数
该函数用于在客户端通过socket套接字建立网络连接。
- 函数原型
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
- 参数解释
- sockfd socket返回的文件描述符
- serv_addr 储存了服务器端的连接信息其中sin_add是服务端的地址
- addrlen serv_addr的长度
- 返回值
成功 0
失败 -1
二、在Ubuntu下编译并运行
查看while程序源代码
- 服务端源代码
#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>
#include <unistd.h>
#include <arpa/inet.h>
#define portnumber 3333
int main(int argc, char* argv[]) {
int local_listen_socket, server_session_socket;
struct sockaddr_in server_addr_info_struct;
struct sockaddr_in client_addr_info_struct;
int size_of_sockaddr_in;
int read_got_bytes_nr;
char buffer[1024];
/* socket: 服务器端开始建立sockfd描述符 */
if ((local_listen_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // AF_INET i.e. IPV4; SOCK_STREAM i.e. TCP
fprintf(stderr, "Socket error:%s\n\a", strerror(errno));
exit(1);
}
/* 准备 sockaddr结构及其内部IP、端口信息 */
bzero(&server_addr_info_struct, sizeof(struct sockaddr_in)); // 初始化,置0
server_addr_info_struct.sin_family = AF_INET; // Internet
server_addr_info_struct.sin_addr.s_addr = htonl(INADDR_ANY); // 将本机host上的long数据转化为网络上的long数据,使服务器程序能运行在不同CPU的主机上
// INADDR_ANY 表示主机监听任意/所有IP地址。
//server_addr_info_struct.sin_addr.s_addr=inet_addr("192.168.1.1"); //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
server_addr_info_struct.sin_port = htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
/* bind: 绑定sockfd描述符 和 IP、端口 */
if (bind(local_listen_socket, (struct sockaddr*)(&server_addr_info_struct), sizeof(struct sockaddr)) == -1) {
fprintf(stderr, "ERR bind():%s\n\a", strerror(errno));
exit(1);
}
/* 设置允许连接的最大客户端数 */
if (listen(local_listen_socket, 5) == -1) {
fprintf(stderr, "ERR listen():%s\n\a", strerror(errno));
exit(1);
}
while (1) {
size_of_sockaddr_in = sizeof(struct sockaddr_in);
fprintf(stderr, "Listening & Accepting...\n");
if ((server_session_socket = accept(local_listen_socket, (struct sockaddr*)(&client_addr_info_struct), &size_of_sockaddr_in)) == -1) { // 服务器阻塞, 直到接受到客户连接
fprintf(stderr, "ERR accept():%s\n\a", strerror(errno));
exit(1);
}
fprintf(stderr, "Got connection from %s\n", inet_ntoa(client_addr_info_struct.sin_addr)); // 网络地址 转换成 字符串
if ((read_got_bytes_nr = read(server_session_socket, buffer, 1024)) == -1) {
fprintf(stderr, "ERR read():%s\n", strerror(errno));
exit(1);
}
buffer[read_got_bytes_nr] = '\0';
printf("Server received %s\n", buffer); /* 这个对话服务已经结束 */
close(server_session_socket); /* 下一个 */
}
/* 结束通讯 */
close(local_listen_socket);
exit(0);
}
- 客户端源代码
#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>
#include <unistd.h>
#include <arpa/inet.h>
#define portnumber 3333
int main(int argc, char* argv[]) {
int local_socket;
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);
}
/* 使用hostname查询host 名字 */
if ((host = gethostbyname(argv[1])) == NULL) {
fprintf(stderr, "ERR gethostbyname\n");
exit(1);
}
/* 客户程序开始建立 local_socket描述符 */
if ((local_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // AF_INET:Internet;SOCK_STREAM:TCP
fprintf(stderr, "ERR socket:%s\a\n", strerror(errno));
exit(1);
}
/* 客户程序填充服务端的资料 */
bzero(&server_addr, sizeof(server_addr)); // 初始化,置0
server_addr.sin_family = AF_INET; // IPV4
server_addr.sin_port = htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
server_addr.sin_addr = *((struct in_addr*)host->h_addr); // IP地址
/* 客户程序发起连接请求 */
if (connect(local_socket, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1) {
fprintf(stderr, "ERR connect:%s\a\n", strerror(errno));
exit(1);
}
/* 连接成功了 */
printf("Please typein a string:\n");
/* 读取和发送数据 */
fgets(buffer, 1024, stdin);
write(local_socket, buffer, strlen(buffer));
/* 结束通讯 */
close(local_socket);
exit(0);
}
编译并运行
- 编译
$ gcc server-while-tcp.c -o server-while- tcp.out
$ gcc cilent.c -o client.out
- 运行
$ ./server-while-tcp.out //在服务端输入
$ ./client.out 服务端IP地址 //在客户端输入
- 服务端页面
- 客户端页面
三、修改服务器为多线程模式
源代码
- server服务端源代码
#include <sys/types.h> /* 网络编程所需头文件*/
#include <sys/socket.h> /* 网络编程所需头文件*/
#include <string.h>
#include <netinet/in.h> /* 包含类似inet_ntoa函数的头文件 */
#include <arpa/inet.h> /* 包含类似inet_ntoa函数的头文件 */
#include <unistd.h> /* 包含close函数、forck函数等系统调用函数的头文件 */
#include <stdio.h>
#include <signal.h> /* 包含signal函数的头文件 */
#define SERVER_PORT 8888 /* 监听端口号 */
#define BACKLOG 10 /* listen函数中最大同时监听连接路数 */
/* socket
* bind
* listen
* accept
* send/recv
*/
int charup(unsigned char ch[1000]);
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr; /* 存放服务器端的通讯协议族、要监听的端口号等信息的结构体 */
struct sockaddr_in tSocketClientAddr; /* 存放连接的客户端的IP地址等信息的结构体 */
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
signal(SIGCHLD, SIG_IGN); /* 处理僵死进程,如果不加,被关闭的客户端所创建的服务器端子进程的资源将不会被父进程回收,导致资源浪费 */
iSocketServer = socket(AF_INET, SOCK_STREAM, 0); /* 创建socket */
if (-1 == iSocketServer)
{
printf("Socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 将short型的端口数据转换成适合于网络传输的数据类型 */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 允许和任何的主机通信 */
memset(tSocketServerAddr.sin_zero, 0, 8); /* 内存置0,确保和struct sockaddr的长度相同 */
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); /* 之前创建的socket文件描述符将被bind修饰 */
if (iRet == -1)
{
printf("Bind error!\n");
return -1;
}
iRet = listen(iSocketServer, BACKLOG); /* 调用listen函数来监听 */
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}
else
{
printf("Listening & Accepting...\n");
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
/* 调用accept函数来等待客户端来连接,客户连接成功返回一个值,连接失败返回-1; */
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++;
/* 支持多个客户端连接,每有一个就调用fork(),并创建一个子进程 */
printf("Get connnect from NO.%d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork()) /* 执行到fork()后马上复制一个代码完全一样的子进程*/
{ /* 父进程走fork()=0;子进程走fork()!=0; */
/*子进程的源码*/
while (1)
{
/* 接受客户端发来的数据并显示出来 */
iRecvLen = iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient); /* 一直接受客户端传来的消息 */
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0'; /* 加上结束符 */
printf("Get Msg From client %d : %s\n", iClientNum, ucRecvBuf);
}
charup(ucRecvBuf); /* 字符串转大写字母函数 */
iRecvLen = send(iSocketClient, ucRecvBuf, strlen(ucRecvBuf), 0);
if(iRecvLen <= 0)
{
}
}
}
}
close(iSocketServer);
return 0;
}
// 定义字符串转大写字母函数
int charup(unsigned char ch[1000])
{
int i = 0;
while (ch[i] != '\0')
{
if(ch[i]>='a'&&ch[i]<='z')
ch[i]=ch[i]-32; //如果你忘记了大小写之间相差32的值,也可以用'a'-'A'来表示
i ++;
}
return 0;
}
- client客户端源代码
#include <sys/types.h> /* 网络编程所需头文件*/
#include <sys/socket.h> /* 网络编程所需头文件*/
#include <string.h>
#include <netinet/in.h> /* 包含类似inet_ntoa函数的头文件 */
#include <arpa/inet.h> /* 包含类似inet_ntoa函数的头文件 */
#include <unistd.h> /* 包含close函数、forck函数等系统调用函数的头文件 */
#include <stdio.h>
#include <signal.h>
#define SERVER_PORT 8888 //同一端口号
/* socket //系统调用
* connect
* listen
* send/recv
*/
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet; //返回值
unsigned char ucSendBuf[1000];
unsigned char ucRecvBuf[1000];
int iSendLen;
int iRecvLen;
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); //host to net,short
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; //本机上所有IP
if(0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0 , 8);
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("connect error!\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
while (1)
{
if(fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if(iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
iRecvLen = iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient); //一直接受客户端传来的消息
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0'; //加上结束符
printf("Feedback:%s\n", ucRecvBuf);
}
}
return 0;
}
编译并运行
- 编译
$ gcc server.c -o server.out
$ gcc client.c -o client.out
- 运行
$ ./server.out //服务端输入
$ ./client.out IP地址 //客户端输入
- 服务端界面
- 第一个客户端界面(字母转大写)
- 第二个客户端界面(输出学号)