0:TCP/IP协议栈与数据包封装
CP/IP网络协议栈分为应用层(Application)、传输层(Transport)、网络层(Network)和链路层(Link)四层。
不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。。IP地址是标识网络中不同主机的地址,而端口号就是同一台主机上标识不同进程的地址,IP地址和端口号合起来标识网络中唯一的进程。
1:基于TCP服务端和客户端系统调用
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
2:socket 地址说明
Generic Socket Address Structures通用socket address
struct sockaddr {
sa_family_t sa_family; /* Address family (AF_* constant) */
char sa_data[14]; /* Socket address (size varies
according to socket domain) */
};
struct sockaddr_in { /* IPv4 socket address */
sa_family_t sin_family; /* Address family (AF_INET) */
in_port_t sin_port; /* Port number */
struct in_addr sin_addr; /* IPv4 address */
unsigned char __pad[X]; /* Pad to size of 'sockaddr'
structure (16 bytes) */
};
struct in_addr { /* IPv4 4-byte address */
in_addr_t s_addr; /* Unsigned 32-bit integer */
};
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);Returns 0 on success, or –1 on error
对于IPv4,family参数指定为AF_INET。
对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。
对于UDP协议,type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。
protocol参数的介绍从略,指定为0即可。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); Returns 0 on success, or –1 on error
int listen(int sockfd, int backlog); Returns 0 on success, or –1 on error
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); Returns file descriptor on success, or –1 on error
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);Returns 0 on success, or –1 on error
比如在bind(),accept(),connect()中的函数中都使用的是struct sockaddr地址,但是在实际的使用中使用的是:struct sockaddr_in地址。因此代码中常用的地址格式定义如下:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("192.168.100.244");
servaddr.sin_port = htons(SERV_PORT);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("192.168.100.244");
servaddr.sin_port = htons(SERV_PORT);
struct sockaddr_in中的成员struct in_addr sin_addr表示32位的IP地址。实际中多数使用点分十进制的字符串表示IP地址,以下函数可以实现字符串表示和in_addr表示之间转换。
字符串转in_addr的函数:
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family, const char *strptr, void *addrptr);
in_addr转字符串的函数:
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family, const char *strptr, void *addrptr);
in_addr转字符串的函数:
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
3:TCP服务端和客户端代码框架。
服务端:通过server ip和port 返回socketID
int getServerSokcketId(int argc, char *argv[])
{
struct sockaddr_in servaddr;
int listenfd,optval = 1;;
if (argc != 3)
{
fputs("usage: ./server serverIp serverPort\n", stderr);
exit(1);
}
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
{
close(listenfd);
perror("create server socket ....");
exit(0);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(atoi(argv[2]));
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval,sizeof(optval)) == -1)
{
close(listenfd);
perror("server socket setsockopt....");
return -1;
}
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
{
perror("server bind ....");
close(listenfd);
exit(0);
}
if(listen(listenfd, 20) == -1)
{
perror("server listen ....");
close(listenfd);
exit(0);
}
printf("Accepting connections ...\n");
return listenfd;
}
main函数调用,同时完成对客户端的请求处理。
int main(int argc, char *argv[])
{
struct sockaddr_in cliaddr;
socklen_t cliaddr_len;
int listenfd, acceptFd;
listenfd = getServerSokcketId(argc,argv);
while(1)
{
cliaddr_len = sizeof(cliaddr);
acceptFd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); /* Wait for connection */
if (acceptFd == -1)
{
syslog(LOG_ERR, "Failure in accept(): %s",strerror(errno));
continue; /* Try next */
}
else
{
printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
}
#if 0
switch (fork()) //使用多进程处理用户的请求
{ /* Create child for each client */
case -1:
printf("------------------------------fork() return error\n");
syslog(LOG_ERR, "111Can't create child (%s)",strerror(errno));
close(acceptFd); /* Give up on this client */
break; /* May be temporary; try next client */
case 0: /* Child */
close(listenfd); /* Don't need copy of listening socket */
handleRequest(acceptFd);
printf("----handleRequest execute over--------------------\n");
_exit(EXIT_SUCCESS);
default: /* Parent */
close(acceptFd); /* Don't need copy of connected socket */
break; /* Loop to accept next connection */
}
#endif
clientProcess(acceptFd);//使用多线程处理客户端的请求。
}
}
其中函数clientProcess(acceptFd);定义如下:
void *thr_fn1(int cfd)
{
ssize_t numRead,numSocketRead,numSocketWrite;
char message[1000];
while(1)
{
if((numSocketRead = read(cfd, &message, sizeof(message))) > 0)
{
//此处对收到的message进行处理
numSocketWrite=write(cfd, &message, sizeof(message));
if ((numSocketWrite != sizeof(message)) || numSocketWrite == -1)
{
syslog(LOG_ERR, "write() failed: %s", strerror(errno));
return(EXIT_FAILURE);
}
}
f(numSocketRead <= 0)
{
printf("process pid:%d exit\n",getpid());
close(cfd);
syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
return(EXIT_FAILURE);
}
}
}
void clientProcess(int cfd)
{
pthread_t tid1;
pthread_create(&tid1, NULL, thr_fn1, cfd);
}
客户端代码
以下函数返回客户端socketID跟服务端类似。
int getClietnSocketId(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
if (argc != 3)
{
fputs("usage: ./client serverIp serverPort\n", stderr);
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1)
{
perror("create socket....\n");
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1], &servaddr.sin_addr.s_addr);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr.s_addr) <= 0)
{
printf("set ip address error!\n");
exit(0);
}
//inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr);
if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))==-1)
{
perror("connect....");
exit(0);
}
return sockfd;
}
main函数
int main(int argc, char *argv[])
{
int sfd=0,numRead;
char parentMsg[1000];
char message[1000];
char buf[BUF_SIZE];
memset(&message,0,sizeof(message));
memset(&parentMsg,0,sizeof(parentMsg));
sfd= getClietnSocketId(argc,argv);
if (sfd == -1)
{
errExit("inetConnect");
}
switch (fork())
{
case -1:
errExit("fork");
case 0: /* Child: read server's response, echo on stdout */
memset(&message,0,sizeof(message));
for (;;) {
//memset(&message,0,sizeof(struct fileMessage));
mutex = pthread_mutex_lock(&mtx);
if (mutex != 0)
printf("pthread_mutex_lock");
if((numRead = read(sfd, &message, sizeof(message)))>0)
{
}
mutex = pthread_mutex_unlock(&mtx);
if (mutex!= 0)
printf("pthread_mutex_unlock");
if (numRead <= 0) /* Exit on EOF or error */
{
printf("server has closed!..%s\n",strerror(errno));
break;
}
}
printf("-------------fork exit, message: %s----------------------------------\n",strerror(errno));
_exit(EXIT_SUCCESS);
default: /* Parent: write contents of stdin to socket */
while(1)
{
if((numRead = read(STDIN_FILENO, buf, BUF_SIZE))>0)
{
if((write(sfd, &parentMsg, sizeof(parentMsg))) != sizeof(parentMsg))
{
fatal("write() failed");
close(sfd)
}
}
}
}
exit(EXIT_SUCCESS);
}
}