一个简单的C/S模型,TCP链接,客户端发送消息,服务端展示到标准输出。通过这个例子,完整的将socket api,多线程/多进程模型,epoll/select,以及并发编程reactor和proactor设计模式。
先来认识API。对于一个建立TCP链接的过程,先对于CS模型来说,Server端:建立socket->绑定端口->监听请求->接受请求。Client端:建立socket->发起连接请求。
这些过程都有对应的api提供。通过例程和注释来看。
SimpleServer.cpp
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(int argc,char** argv)
{
int socketfd,readfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addr_size;
char buffer[1024];
int port = atoi(argv[1]);
//new fd
if ((socketfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
printf("Socket Error:%s\n",strerror(errno));
exit(-1);
}
//init server addr
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port);
//bind port
if(bind(socketfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1)
{
printf("Bind Error:%s\n",strerror(errno));
exit(-1);
}
//listend the port
if (listen(socketfd,5))
{
printf("Listen Error:%s\n",strerror(errno));
exit(-1);
}
while(1)//handler client one by one
{
//server for accept
if ((readfd = accept(socketfd,(struct sockaddr*)(&client_addr),&addr_size)) == -1)
{
printf("Accept Error:%s\n",strerror(errno));
exit(-1);
}
printf("Accept Client Address:%s\n",inet_ntoa(client_addr.sin_addr));
int readbytes = 0;
//read until end
//int number = 0;
while((readbytes = read(readfd,buffer,1024))>0)
{
//printf("Recived Content:%s\n",buffer);
write(STDOUT_FILENO,buffer,strlen(buffer));
fflush(stdout);
memset(buffer,0,1024);
}
if (readbytes == 0)
{
printf("Read Finished!\n");
}else{
printf("Read Terminate Error:%s\n",strerror(errno));
exit(-1);
};
close(readfd);
}
close(socketfd);
exit(0);
}
SimpleClient.cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(int argc,char **argv)
{
int socketfd;
struct in_addr server_ip;
struct sockaddr_in server_addr;
struct hostent *host;
char buffer[1024];
if ((socketfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
printf("Socket Error:%s\n",strerror(errno));
exit(-1);
}
inet_aton(argv[1],&server_ip);
//init server address
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr = server_ip;
if (connect(socketfd,(struct sockaddr*)&server_addr,sizeof(sockaddr_in)) == -1)
{
printf("Connect Server Error:%s\n",strerror(errno));
exit(-1);
}
while(fgets(buffer,1024,stdin) != NULL)
{
// int number = htonl(atoi(buffer));
// write(socketfd,&number,sizeof(int));
// if (strlen(buffer) != (sizeof(buffer)-1))
// {
// buffer[strlen(buffer)-1] = '\0';
// }
write(socketfd,buffer,strlen(buffer));
printf("Send Message:%s",buffer);
if (strcmp(buffer,"exit\n")==0)
{
printf("Exit Client!\n");
close(socketfd);
exit(0);
}
};
close(socketfd);
exit(0);
}
总结知识点:
1、socket\bind\listen\accept\connect的调用方式
2、网络字节序和主机字节序的转换(只有数字型需要转换,char等字符串型不需要转换;由于需要三次握手,端口等需要字节序换转)