预备知识
套接字
套接字(socket)这个词可以表示很多概念:在TCP/IP协议中,”IP+地址+TCP/UDP端口号”唯一标示网络通讯中的一个进程,IP地址+端口号”就称为socket。
网络字节序
网络中要实现通信,少不了数据的传输。所以这里就引入了网络字节序的概念。
在前边的学习中,我们接触到大端和小端的概念。小端:数据的地位在低地址,高位在高地址;大端:数据的低位在高地址,高位在低地址。网络数据流同样也有大端和小端之分。网络数据流先发出的是低地址,后发出的是高地址。TCP/IP规定,网络数据流采用大端字节序,即就是低位在高地址。我们之所以会说到大端和小端?是因为,网络通信的时候必须知道端口号,如果发送端是大端字节序,接收端是小端字节序,那么最后看到的端口号就是不正确的端口号,所以,我们必须将端口号在发送端和接收端之间转换成统一的字节序形式。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //主机字节序转为网络字节序,long类型
uint16_t htons(uint16_t hostshort); //主机字节序为网络字节序,short类型
uint32_t ntohl(uint32_t netlong); //网络节序转为主机字节序,long类型
uint16_t ntohs(uint16_t netshort); //主机节序转为网络字节序,short类型
socket编程的操作函数
1.套接字创建函数sockt:
函数功能:创建套接字。
参数:
(1)domain:表示建立的socket的类型,可选择的类型如下:
对于IPv4,参数指定为AF_NET
(2)type:表示的是传输的数据是 数据报传输还是字节流传输,可选的类型如下:
对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。
如果是UDP协议,type参数指定为SOCK_DGRAM表示面向数据报的协议。
(3)protocol:表示创建的方式,我们在这里把他置为0。
返回值:
成功返回一个文件描述符,失败返回-1。
2.服务器绑定函数bind
函数功能:服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。
参数:
(1)sockfd:服务器的套接字,也就是socket函数的返回结果。
(2)addr:是socket服务器的地址内容,结构体内的变量则去填写ip地址和端口号。而sockaddr_in这个结构体成员如下:
这里有设计到了字符串与in_addr的转换:
(3)addrlen:指定结构体的长度。
返回值:成功返回0,失败返回-1。
3.设置监听状态函数listen
函数功能:用来设置sockfd套接字为监听状态的,用来监听客户端的连接。
参数:
backlog:表示的是服务器连接达到最大的数量之后,还可以放到等待队列的连接个数,所以一般不要设太大。
返回值:成功返回0,失败返回-1。
4.请求连接函数connect
函数功能:用于客户端,用来请求对服务器的连接。
参数:
sockfd表示的表示的是要链接到服务器的客户端套接字。
参数addr表示的是服务器的地址与端口号;
参数addrlen表示的是addr的大小一般使用sizeof得到。
5.接受连接函数accept
函数功能:三次握手完成后,服务器调用accept()接受连接,如果是服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
参数:
sockfd:已经创建好的文件描述符,也是监听套接字。
addr:输出型参数,获取客户端的信息(IP地址,端口号等)。
addrlen:是一个传入传出参数,传入的是调用这提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。
返回值:成功返回一个文件描述符,是真正的用于通信的桃姐子。失败返回-1。
单进程的套接字TCP通信
server.c:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
void Usage(const char* name)
{
printf("Usage:%s [IpAddress] [port]\n",name);
}
int StartUp(int port, const char* ip)
{
int ListenSock = socket(AF_INET,SOCK_STREAM,0);
if(ListenSock < 0)
{
perror("socket");
return 1;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("bind");
return 2;
}
if(listen(ListenSock,5) < 0)
{
perror("listen");
return 3;
}
return ListenSock;
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listenSock = StartUp(atoi(argv[2]),argv[1]);
struct sockaddr_in client;
socket_t len = sizeof(client);
while(1)
{
int sock = accept(listenSock,(struct sockaddr*)&client,&len);//获取客户机的信息
if(sock < 0)
{
perror("accept");
continue;
}
printf("Get a client,IP is %s,port is %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
char buf[1024];
while(1)
{
ssize_t s = read(sock,buf,sizeof(buf)-1);//从服务器读数据
if(s == 0)
{
//数据已经读完了,客户端不发送数据了
printf("client is quit!\n");
return 2;
}
else if(s <0)
{
perror("read");
return 3;
}
else
{
buf[s] = 0;
printf("client# %s\n",buf);
// write(sock, buf, strlen(buf));
printf("say: ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s <= 0)
{
perror("read");
return 1;
}
buf[_s-1]=0;
write(sock,buf,sizeof(buf)-1);
}
}
close(sock);
}
return 0;
}
client.c:
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
void Usage(const char* name)
{
printf("Usage:%s [IpAddress] [port]\n",name);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socke");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
{
perror("connect");
return 3;
}
//printf("connect success!\n");
char buf[1024];
while(1)
{
printf("send# ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s <= 0)
{
perror("read");
return 4;
}
buf[s-1] = 0;
write(sock,buf,strlen(buf));
ssize_t _s = read(sock, buf, sizeof(buf)-1);
if(_s == 0)
{
printf("server quit\n");
break;
}
else if(_s < 0)
{
perror("read");
return 5;
}
else
{
buf[s]=0;
printf("server #: %s\n", buf);
}
}
close(sock);
return 0;
}
多进程套接字TCP通信
上边的代码,只可以一个客户端进行发送数据,而在实际的应用中,,都是会出现多个客户端给服务器发送数据,所以,上边的实现并不实用。所以,我们可以实现一个多进程的socket通信,以实现多个客户端给服务器发数据。
实现方法:服务器端可以创建多个子进程去处理客户端发来的信息。当每次收到一个新的客户端的连接请求的时候,我们就会fork()出一个子进程,父进程用于等待子进程,子进程用于执行 读客户端发的数据 的操作。细心的你可能会发现,我们在子进程读取信息之前还进行了一次fork(),这是为什么呢?其实,我们用子进程fork()出一个孙子进程,终止掉儿子进程,儿子进程被它的父进程回收,此时的孙子进程就是一个孤儿进程,被1号进程领养。这样做的目的就是,不要让儿子进程等待孙子进程太久而消耗太多的系统资源。
代码实现:
server.c:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
void Usage(const char* name)
{
printf("Usage:%s [IP] [port]\n",name);
}
int StartUp(int port, const char* ip)
{
int ListenSock = socket(AF_INET,SOCK_STREAM,0);
if(ListenSock < 0)
{
perror("socket");
return 1;
}
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
return 2;
}
if(listen(ListenSock,5) < 0)
{
perror("listen");
return 3;
}
return ListenSock;
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listenSock = StartUp(atoi(argv[2]),argv[1]);
struct sockaddr_in client;
socklen_t len = sizeof(client);
while(1)
{
int sock = accept(listenSock,(struct sockaddr*)&client,&len);
if(sock < 0)
{
perror("accept");
continue;
}
printf("get a client is %s,port is %d\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
int id = fork();
if(id > 0)
{
close(sock);
while(waitpid(-1,NULL,WNOHANG) > 0);
continue;
}
else
{
close(listenSock);
if(fork() > 0)
{
exit(0);
}
char buf[1024];
while(1)
{
ssize_t s = read(sock,buf,sizeof(buf)-1); //服务器进行读数据
if(s > 0)
{
buf[s] = 0;
printf("client# %s\n",buf);
}
else
{
//数据已经读完了,客户端不发送数据了
printf("client is quit!\n");
break;
}
}
close(sock);
break;
}
}
return 0;
}
client.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void Usage(const char* name)
{
printf("Usage:%s [IP] [port]",name);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0)
{
perror("connec");
return 3;
}
char buf[1024];
while(1)
{
printf("send# ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s < 0)
{
perror("read");
break;
}
buf[s-1] = 0;
write(sock,buf,s);
}
close(sock);
return 0;
}
多线程的套接字通信
实现方法:
主线程中创建出一个新线程,新线程的执行函数是读取信息。类似于上边的多进程间的通信,我们可以将新的线程进行分离,分离之后的线程就不需要主线程去等待,而是由操作系统区回收。(这里我们不可以join新线程,如果这样做的话,主线程还是需要花费很长的时间去等待,所以,新的线程还是由系统去回收) 。
代码实现:
server.c
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<pthread.h>
void Usage(const char* name)
{
printf("Usage is %s [IP] [port]\n",name);
}
int StartUp(int port,const char* ip)
{
int ListenSock = socket(AF_INET,SOCK_STREAM,0);
if(ListenSock < 0)
{
perror("socket");
return 1;
}
int opt = 1;
setsockopt(ListenSock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(ListenSock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
perror("bind");
return 2;
}
if(listen(ListenSock,5) < 0)
{
perror(("listen"));
return 3;
}
return ListenSock;
}
void* thread_hander(void* arg)
{
int sock = *((int*)arg);
char buf[1024];
while(1)
{
ssize_t _s = read(sock,buf,sizeof(buf)-1);
if(_s > 0)
{
buf[_s-1]=0;
printf("client say# %s\n",buf);
if(write(sock,buf,sizeof(buf)-1) < 0)
{
break;
}
}
else if(_s == 0)
{
printf("client is quit!\n");
break;
}
else
{
perror("read");
break;
}
}
close(sock);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int listenSock = StartUp(atoi(argv[2]),argv[1]);
struct sockaddr_in client;
int len = 0;
while(1)
{
int sock = accept(listenSock,(struct sockaddr*)&client,&len);
if(sock < 0)
{
perror("accept");
continue;
}
printf("Get a client! IP is %d\n,port is %d\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
pthread_t tid;
int ret = pthread_create(&tid,NULL,thread_hander,&sock);
if(ret < 0)
{
perror("pthread_create");
return 3;
}
pthread_detach(tid);
}
return 0;
}
server.c:
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
void Usage(const char* name)
{
printf("Usage is %s [IP] [port]\n",name);
}
int main(int argc,const char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
int ret = connect(sock,(struct sockaddr*)&server,sizeof(server));
if(ret < 0)
{
perror("connect");
return 3;
}
char buf[1024];
while(1)
{
printf("send# ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s > 0)
{
buf[_s -1 ] = 0;
if(write(sock,buf,sizeof(buf)-1) < 0)
{
break;
}
ssize_t s = read(sock,buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("server echo# %s\n",buf);
}
}
else
{
perror("read");
return 4;
}
}
return 0;
}