基础知识
1.TCP
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
2.嵌套字
套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。
函数调用
1int socket(int domain, int type,int protocol)
参数 | 解释 | 用法 |
---|---|---|
domain | 网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). | AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信(当我们 man socket时发现 domain可选项是 PF_而不是AF_,因为glibc是posix的实现所以用PF代替了AF,不过我们都可以使用的). |
type | 网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) | SOCK_STREAM表明使用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信. |
protocol | 由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 |
socket为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况
2int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
参数 | 解释 |
---|---|
sockfd | 由socket调用返回的文件描述符 |
addrlen | sockaddr结构的长度 |
my_addr | sockaddr结构的长度 |
3int listen(int sockfd,int backlog)
参数 | 解释 |
---|---|
sockfd | 是bind后的文件描述符 |
backlog | 设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度. |
4int accept(int sockfd, struct sockaddr *addr,int *addrlen)
参数 | 解释 |
---|---|
sockfd | socket返回的文件描述符 |
serv_add | 储存了服务器端的连接信息.其中sin_add是服务端的地址 |
addrlen | serv_addr的长度 |
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符 失败时返回-1.
编译并运行
编译
gcc -o server-while-tcp.out server-while-tcp.c gcc -o client.out client.c
运行
./server-while-tcp.out ./client.out 192.168.1.*** //新的窗口打开
修改服务器为多线程模式
优点 1.多线程发送数据,即一个服务器对应多个客户端,最多可以同时监听10个客户端。
2.客户端向服务器发起连接请求,成功后打印连接信息。
3.客户端向服务器发送数据后,服务器端打印数据。
4.服务器将得到的数据全部转换为大写后返回到客户端。
5.客户端打印出从服务器端返回的数据。
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(iSocketClient); return -1; } } } } } 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 -o srv srv.c gcc -o cli cli.c
./srv ./cli 192.168.1.*** //新的窗口打开