本文将实现一个最简单的客户端/服务器程序。以下所述均属个人理解,如有不对,欢迎批评。所谓网络编程就是调用操作系统提供的函数实现部署在不同地域的应用程序间的数据交换。
基本概念:客户端是指网络通信中主动发起连接的一方,服务器是指被动等待连接的一方。套接字是一个抽象层,可以将其理解为一个特殊的文件句柄,应用程序可以通过它向网络中写入或读取数据,即发送或接收数据。也就是说应用程序通过套接字连接到网络中。TCP/IP中的套接字类型有流套接字和数据报套接字,分别对应TCP和UDP协议。
网络中的每一台主机由一个唯一IP地址标识,主机用运行的一个网络应用程序由一个唯一的端口号标识,端口号的范围一般为1~65535。一个IP地址和一个端口号可以唯一确定一个网络应用程序。想要连接到网络中的一台主机,只需要知道其IP地址和端口号。
服务器端编写:服务器的职责是等着客户端的连接,有客户连接的话,则创建一个新的套接字与其进行通信,此时客户端和服务器端就没啥区别了。
TCP服务器端的编写步骤如下:
1)创建TCP套接字,告诉套接字层使用的协议,套接字类型和IP版本
2)将套接字绑定到特定的IP地址和端口号上,告诉套接字层本网络程序使用这个端口和主机的这些IP(一台主机可能有多个IP地址)。
3)对端口进行监听,告诉系统这个端口可以接受外部连接了
4)反复执行以下操作
l 等待客户端的连接,当有新连接来时,创建新的套接字为其服务
l 使用新的套接字和客户端进行数据交换
l 完成数据交换时,关闭新套接字。
TCP客户端的编写步骤如下:
1)创建TCP套接字,告诉套接字层使用的协议,套接字类型和IP版本
2)连接远程的服务器端,其实也需要绑定到特定的端口上,可以默认执行或者显示执行
3)和远程服务器进行数据交换
4)关闭套接字
系统定义常量:ipv4 AF_INET (address family internet protocol)
Ipv6 AF_INET6(address family internet protocol version 6)
流套接字SOCK_STREAM
数据报套接字SOCK_DGRAM
为了实现上述步骤,Linux提供了以下函数和结构体
1) 创建套接字
int socket (int domain,int type,int protocol);
第一个domain指定ip版本号,常见值AF_INET或者AF_INET6
第二个type指定套接字类型,常见值SOCK_STREAM或者SOCK_DGRAM
第三个protocol指所用协议,常见值有IPPROTO_TCP 或者IPPROTO_UDP
返回非负值表示成功,-1表示失败,非负值代表套接字句柄。
例:int sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
2)地址结构体:包括通用地址,ipv4地址,ipv6地址。
通用地址:
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
IPV4地址
struct in_addr{
uint32_t s_addr;
};
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
IPV6地址
struct in6_addr{
uint32_t s_addr[4];
};
struct sockaddr_in6{
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
3)连接套接字
int connect (int socket,const struct sockaddr *foreignAddress,socklen_t addressLength)
4)绑定套接字
int bind(int socket,struct sockaddr *localAddress,socklen_t addressLength);
0成功 -1失败
5)处理进入连接
int listen(int socket,int queueLimit)
返回0成功,-1失败 queueLImit最大等待连接队列
int accept(int socket,struct sockaddr *clientAddress,socklen_t *addressLength);
成功返回新的套接字句柄,失败返回-1
5)通信
ssize_t send(int socket,const void *msg,size_t msgLength,int flags)
ssize_t recv(int socket,void *rcvBuffer,size_t bufferLength,int flags)
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
if(argc<3||argc>4)
{
puts("Parameters(s):<Server Address> <Echo Word> [<Server Port>]");
exit(1);
}
char *servIP=argv[1];
char *echoString=argv[2];
in_port_t servPort =(argc==4)?atoi(argv[3]):7;
int sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock<0)exit(1);
struct sockaddr_in clntAddr;
memset(&clntAddr,0,sizeof(clntAddr));
clntAddr.sin_family=AF_INET;
clntAddr.sin_addr.s_addr=htonl(INADDR_ANY);
clntAddr.sin_port=htons(20000);
if(bind(sock,(struct sockaddr*)&clntAddr,sizeof(clntAddr))<0)
exit(1);
struct sockaddr_in servAddr;
memset(&servAddr,0,sizeof(servAddr));
servAddr.sin_family=AF_INET;
int rtnVal=inet_pton(AF_INET,servIP,&servAddr.sin_addr.s_addr);
if(rtnVal<=0)
exit(1);
servAddr.sin_port=htons(servPort);
if(connect(sock,(struct sockaddr *) &servAddr,sizeof(servAddr))<0)
exit(1);
size_t echoStringLen=strlen(echoString);
ssize_t numBytes=send(sock,echoString,echoStringLen,0);
size_t totalBytesRcvd=0;
fputs("Received:",stdout);
while(totalBytesRcvd<echoStringLen)
{
char buffer[BUFSIZE];
numBytes=recv(sock,buffer,BUFSIZE-1,0);
totalBytesRcvd+=numBytes;
buffer[numBytes]='\0';
fputs(buffer,stdout);
}
fputc('\n',stdout);
close(sock);
exit(0);
}
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static const int MAXPENDING=5;
void HandleTCPClient(int clntSocket);
void printAddrInMsg(char *msg,struct sockaddr_in addr);
int main(int argc,char *argv[])
{
if(argc!=2)
{
puts("Parameter(s):<Server Port>");
exit(1);
}
in_port_t servPort=atoi(argv[1]);
int servSock;
if((servSock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
exit(1);
struct sockaddr_in servAddr;
memset(&servAddr,0,sizeof(servAddr));
servAddr.sin_family=AF_INET;
servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
servAddr.sin_port=htons(servPort);
if(bind(servSock,(struct sockaddr*) &servAddr,sizeof(servAddr))<0)
exit(1);
if(listen(servSock,MAXPENDING)<0)
exit(1);
while(1)
{
struct sockaddr_in clntAddr;
socklen_t clntAddrLen =sizeof(clntAddr);
int clntSock=accept(servSock,(struct sockaddr*)&clntAddr,&clntAddrLen);
if(clntSock<0)
exit(1);
char clntName[INET_ADDRSTRLEN];
if(inet_ntop(AF_INET,&clntAddr.sin_addr.s_addr,clntName,sizeof(clntName))!=NULL)
printf("Handling client %s/%d\n",clntName,ntohs(clntAddr.sin_port));
else
puts("Unable to get Client address");
HandleTCPClient(clntSock);
}
}
void HandleTCPClient(int clntSocket)
{
char buffer[BUFSIZE];
ssize_t numBytesRcvd=recv(clntSocket,buffer,BUFSIZE,0);
if(numBytesRcvd<0)
exit(1);
while(numBytesRcvd>0)
{
ssize_t numBytesSent=send(clntSocket,buffer,numBytesRcvd,0);
numBytesRcvd=recv(clntSocket,buffer,BUFSIZE,0);
}
struct sockaddr_in clientAddr,serverAddr;
memset(&clientAddr,0,sizeof(clientAddr));
memset(&serverAddr,0,sizeof(serverAddr));
socklen_t clntAddrLen,servAddrLen;
getsockname(clntSocket,(struct sockaddr*)&serverAddr,&servAddrLen);
getpeername(clntSocket,(struct sockaddr*)&clientAddr,&clntAddrLen);
if(servAddrLen==sizeof(serverAddr))printAddrInMsg("finished",serverAddr);
if(clntAddrLen==sizeof(clientAddr))printAddrInMsg("finished",clientAddr);
close(clntSocket);
}
void printAddrInMsg(char *msg,struct sockaddr_in addr)
{
char addrName[INET_ADDRSTRLEN];
if(inet_ntop(AF_INET,&addr.sin_addr.s_addr,addrName,sizeof(addrName))!=NULL)
printf("%s %s/%d\n",msg,addrName,ntohs(addr.sin_port));
else puts(msg);
}