提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
了解TCP/IP协议,熟悉socket网络编程
提示:以下是本篇文章正文内容,下面案例可供参考
一、TCP/IP协议是什么?
TCP/IP虽然叫传输控制协议(TCP)和网络协议(IP),但它实际上是一组协议,包含了上百个功能的协议,如ICMP、RIP、TELNET、FTP、SMTP、ARP、TFTP等,这些协议一起称为TCP/IP协议。
TCP是一种面向连接的网络传输方式,计算机A先呼叫计算机B,计算机B接收连接后发出确认信息,A收到确认信息后发送信息,B完成数据接收以后发送完毕信息,这时再关闭数据连接,TCP是面向连接的可靠信息传输方式。这种方式的缺点是传输过程复杂,需要占用较多的网络资源。
二、端口
端口是指计算机中为了表示计算机中访问网络的不同程序而设的编号,每一个程序在访问网络时都会分配一个标识符,程序在访问网络或接收访问时,会用这个标识符表示这一网络数据属于这个程序。注意:这里的端口是不同程序的逻辑编号,并不是实际存在的。
端口号是一个16位的无符号整数,对应的十进制取值范围是0-65535。小于256的端口是系统的保留端口号,主要用于系统进程通信。例如网站的WWW服务使用的是80号端口,FTP服务使用的是21号端口。不在这一范围的端口号是自由端口,在编程时可以调用这些端口号。
三、socket端口
socket时网络编程的一种接口,一种特殊的I/O。(网络通信的基石)在TCP/IP协议中,“IP地址+TCP或UDP端口号”可以唯一表示网络通信中的一个进程,可以简单地认为“IP地址+端口号”就称为socket。两个进程各有一个socket来表示,这两个socket组成的socket对就唯一标识一个连接。用socket函数建立一个socket连接,此函数返货一个整型的sockett描述符,随后进行数据传输。
通常,socket分为三种类型:流式socket、数据报socket和原始socket。
1.两个重要的数据类型sockaddr和sockaddr_in
这两个结构类型都是用来保存socket信息的,如IP地址、通信端口等。
sockaddr用来保存一个套接字,定义方法如下所示。
struct sockaddr
{
unsigned short int sa_family;
char sa_data[14];
};
在这个结构体中,成员的含义如下所示。
(1)sa_family:指定通信的地址类型。如果是TCP/IP通信,则该值位AF_INET。
(2)sa_data:最多使用14个字符长度,用来保存IP地址和端口信息。
sockaddr_in的功能与sockaddr相同,也是用来保存一个套接字的信息。不同的是,它将IP地址与端口分开位不同的成员。这个结构体的定义方法如下所示。
struct socketaddr_in
{
unsigned short int sa_family;
uint16_t sin_port;
struct in_addr sin_addr;
unsigned char sin_sero[8];
};
这个结构体的成员与作用如下所示:
(1)sin_family:与sockaddr结构体中的sa_family相同。
(2)sin_port:套接字使用的端口号
(3)sin_addr:需要访问的IP地址
(4)sin_zero:未使用的字段,填充为0.(可使用bzero()函数将其置零)
在这一结构体中,in_addr也是一个结构体,作用是保存一个IP地址。其中,sockaddr_in这个结构体使用更方便,它可以轻松处理套接字地址的基本元素
2.基于TCP协议的客户端/服务器程序的常用函数
函数名 | 功能 |
---|---|
socket | 用于建立一个socket连接 |
bind | 将socket与本机上的一个端口绑定,随后就可以在该端口监听服务请求 |
connect | 面向连接的客户程序使用connect函数来配置socket,并与远端服务器建立一个TCP连接 |
listen | listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将达到的服务请求保存在此队列中,直到程序处理它们 |
accept | accept函数让服务器接收客户的连接请求 |
close | 停止在该socket上的任何数据操作 |
send | 数据发送函数 |
recv | 数据接收函数 |
四、建立服务器和客户端通信
1.流程图
2.相关代码
服务器代码:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 5500 // 定义端口号
#define BACKLOG 10 //最大连接个数
#define LENGTH 512 // 数据长度
int main(void)
{
int sockfd; // Socket file descriptor
int client_fd; // New Socket file descriptor
int num;
int sin_size; // to store struct size
char sdbuf[LENGTH]; // Send buffer
struct sockaddr_in addr_local;
struct sockaddr_in addr_remote;
char sendstr[16]= {"123456789 abcde"};
char revbuf[LENGTH]; // Receive buffer
/* 建立socket,返回一个数据结构分配的存储空间 */
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
{
printf ("socket 创建失败!\n");
return (0);
}
else
{
printf ("OK: Obtain Socket Despcritor sucessfully.\n");
}
/* Fill the local socket address struct */
bzero(&addr_local,sizeof(struct sockaddr_in)); //地址结构清零
addr_local.sin_family = AF_INET; // Protocol Family 协议族 AF_INET表示TCP
addr_local.sin_port = htons(PORT); // 将主机的无符号短整型数转换成网络字节序
addr_local.sin_addr.s_addr = htonl(INADDR_ANY); // AutoFill local address 本机任意ip地址(多张网卡)
/* Blind a special Port */
if( bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1 )//绑定本机IP地址及端口信息
{
printf ("端口号 %d 出错!\n",PORT);
return (0);
}
else
{
printf("端口号 %d 成功!\n",PORT);
}
/* Listen remote connect/calling */
if(listen(sockfd,BACKLOG) == -1) //监听socket,建立一个数据队列,等待accept
{
printf ("监听端口 %d 出错!\n", PORT);
return (0);
}
else
{
printf ("监听端口 %d 成功!\n", PORT);
}
while(1)
{
sin_size = sizeof(struct sockaddr_in); //清零,存放地址长度
/* Wait a connection, and obtain a new socket file despriptor for single connection */
if ((client_fd = accept(sockfd, (struct sockaddr *)&addr_remote, &sin_size)) == -1)//阻塞等待直到有客户端连接上来
{
printf ("客户与服务器连接失败.\n");
continue;
}
else
{
printf ("服务器连接成功,收到一个连接来自 %s.\n", inet_ntoa(addr_remote.sin_addr)); //inet_ntoa将一个网络字节顺序的IP地址转换为它对应的点分十进制串
}
/* 子进程代码*/
if(!fork())
{
printf("你能够发送消息,或者输入exit断开连接 。\n");
while(strcmp(sdbuf,"exit") != 0)
{
scanf("%s", sdbuf);
if((num = send(client_fd, sdbuf, strlen(sdbuf), 0)) == -1)
{
printf("发送失败.\n");
close(client_fd);
exit(1);
}
printf("发送 %d 成功,请再次输入.\n", num);
}
}
else
{
while (strcmp(revbuf, "exit") != 0) // Check remoter command
{
bzero(revbuf, LENGTH);
num = recv(client_fd, revbuf, LENGTH, 0);
switch (num)
{
case -1:
printf("连接出错!\n");
close(client_fd);
exit(1);
case 0:
close(client_fd);
printf("断开连接!\n");
return(0);
default:
printf("收到:%d\n", num);
break;
}
revbuf[num] = '\0';
printf("收到了服务器数据: %s\n", revbuf);
}
}
close(client_fd);
while(waitpid(-1, NULL, WNOHANG) > 0);
}
return 0;
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 5500 // 定义端口号
#define LENGTH 512 // 数据长度
int main(int argc, char *argv[])
{
int sockfd; // Socket file descriptor
int num; // Counter of received bytes
char revbuf[LENGTH]; // Receive buffer
struct sockaddr_in remote_addr; // Host address information
char sdbuf[LENGTH]; // Send buffer
/* Check parameters number */
if (argc != 2)
{
printf ("请输入主机的地址\n");
return (0);
}
/* Get the Socket file descriptor */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("创建socket失败\n");
return (0);
}
/* Fill the socket address struct */
bzero(&remote_addr,sizeof(remote_addr));
remote_addr.sin_family = AF_INET; // Protocol Family
remote_addr.sin_port = htons(PORT); // Port number
inet_pton(AF_INET, argv[1], &remote_addr.sin_addr); // Net Address
/* Try to connect the remote */
if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1)
{
printf ("服务器连接错误!\n");
return (0);
}
else
{
printf ("已成功连接到 %s\n",argv[1]);
}
/* Try to connect the server */
if (!fork())
{
printf("你能够发送消息,或者输入exit断开连接 。\n");
while (strcmp(sdbuf, "exit") != 0)
{
scanf("%s", sdbuf);
if ((num = send(sockfd, sdbuf, strlen(sdbuf), 0)) == -1)
{
printf("发送失败.\n");
close(sockfd);
exit(1);
}
printf("发送 %d 成功,请再次输入.\n", num);
}
}
else
{
while (strcmp(revbuf, "exit") != 0) // Check remoter command
{
bzero(revbuf, LENGTH);
num = recv(sockfd, revbuf, LENGTH, 0);
switch (num)
{
case -1:
printf("连接出错!\n");
close(sockfd);
exit(1);
case 0:
close(sockfd);
printf("断开连接!\n");
return(0);
default:
printf("收到:%d\n", num);
break;
}
revbuf[num] = '\0';
printf("收到了服务器数据: %s\n", revbuf);
}
}
close (sockfd);
return 0;
}
3.运行效果
该代码能够实现服务器和客户端的实时通信,但是只能实现1对1的实时通信。
4.代码改进
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#define COUNT 5500 // 定义同时聊天的人数
#define PORT 5500 // 定义端口号
#define BACKLOG 10 //最大主机连接个数
#define LENGTH 1024 // 数据长度
int socket_fd[COUNT]; //保存socket
char sdbuf[LENGTH]; // Send buffer
char revbuf[LENGTH]; // Receive buffer
int num;
int fs;
/*线程入口函数*/
void pthread_function(int client_fd)
{
if (!fork())
{
printf("你能够发送消息,或者输入exit断开连接 。\n");
while (strcmp(sdbuf, "exit") != 0)
{
scanf("%d", &fs);
scanf("%s", sdbuf);
if ((num = send(socket_fd[fs], sdbuf, strlen(sdbuf), 0)) == -1)
{
printf("发送失败.\n");
close(client_fd);
exit(1);
}
printf("发送 %d 成功,请再次输入.\n", num);
}
}
else
{
while (strcmp(revbuf, "exit") != 0) // Check remoter command
{
bzero(revbuf, LENGTH);
num = recv(client_fd, revbuf, LENGTH, 0);
switch (num)
{
case -1:
printf("连接出错!\n");
close(client_fd);
exit(1);
case 0:
close(client_fd);
printf("断开连接!\n");
exit(0);
default:
printf("收到:%d\n", num);
break;
}
revbuf[num] = '\0';
printf("收到了服务器数据: %s\n", revbuf);
}
}
close(client_fd);
/*退出线程*/
pthread_exit(NULL);
}
int main(void)
{
int i;
/*初始化socket数组*/
for (i = 0; i < COUNT; i++)
{
socket_fd[i] = -1;
}
pthread_t id;
int sockfd; // Socket file descriptor
int client_fd; // New Socket file descriptor
socklen_t sin_size;
struct sockaddr_in addr_local;
struct sockaddr_in addr_remote;
/* 建立socket,返回一个数据结构分配的存储空间 */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("socket 创建失败!\n");
return (0);
}
/* 配置信息 */
bzero(&addr_local, sizeof(struct sockaddr_in)); //地址结构清零
addr_local.sin_family = AF_INET; // Protocol Family 协议族 AF_INET表示TCP
addr_local.sin_port = htons(PORT); // 将主机的无符号短整型数转换成网络字节序
addr_local.sin_addr.s_addr = htonl(INADDR_ANY); // AutoFill local address 本机任意ip地址(多张网卡)
/* 绑定端口 */
if (bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1)//绑定本机IP地址及端口信息
{
printf("绑定端口号 %d 出错!\n", PORT);
return (0);
}
else
{
printf("绑定端口号 %d 成功!\n", PORT);
}
/* 监听 */
if (listen(sockfd, BACKLOG) == -1) //监听socket,建立一个数据队列,等待accept
{
printf("监听端口 %d 出错!\n", PORT);
return (0);
}
else
{
printf("监听端口 %d 成功!\n", PORT);
}
i = 0;
while (1)
{
sin_size = sizeof(struct sockaddr_in); //清零,存放地址长度
/* Wait a connection, and obtain a new socket file despriptor for single connection */
if ((client_fd = accept(sockfd, (struct sockaddr*)&addr_remote, &sin_size)) == -1)//阻塞等待直到有客户端连接上来
{
printf("客户与服务器连接失败.\n");
continue;
}
else
{
printf("服务器连接成功,收到一个连接来自 %s.\n", inet_ntoa(addr_remote.sin_addr)); //inet_ntoa将一个网络字节顺序的IP地址转换为它对应的点分十进制串
}
/*找一个可用的socket位置*/
while (socket_fd[i] != -1)
{
i = (i + 1) % COUNT;
}
/*保存socket并启动线程处理*/
socket_fd[i] = client_fd;
pthread_create(&id, NULL, (void*)pthread_function, (int*)client_fd);
}
}
在该次改进中,我在服务器主程序中一直循环的接收客户端的连接,并保存到socket_fd数组中,接收到连接后,为每一个客户端创建一个线程来单独处理,并给线程传入相关的client_fd参数进行信息交换,在线程中使用了父子进程,分别实现和客户端的信息接收、发送。只需要在发送时加入客户端的编号,默认0,1,2···
5.运行结果
总结
在网络编程中,服务器我们需要先建立socket,再绑定端口,建立监听后即可等待客户端的connect请求,即可开始收发数据。若想要实现实时通信和多客户端连接,使用线程和父子进程即可,需要注意的是线程使用时需要注意的是线程是否会因为主程序的结束而结束,看情况添加pthread_join(tid,NUMM)让主程序等待线程运行结束。