本文参考自徐晓鑫《后台开发》,重点复习总结TCP通信流程,读者也可以参考:
http://blog.csdn.net/wqc_csdn/article/details/51513543,谢谢。
一、客户端和服务端操作流程
服务器端:
socket() --> bind() --> listen() --> accept() --> recv() --> close()
创建socket --> 绑定socket和端口号–> 监听端口号–> 接收来自客户端的连接请求–> 从socket中读取字符–> 关闭socket
客户端:
socket() --> connect() --> send() --> close()
创建socket --> 连接指定服务器的IP/端口号–>向socket中写入信息–>关闭socket
二、代码
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#define MAXLINE 4096
int main()
{
int listenfd,connfd;
struct sockaddr_in servaddr;
char buff[4096];
int n;
//创建一个TCP的socket
if( (listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
}
//先把地址清空,检测任意IP
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
//地址绑定到listenfd
if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
printf(" bind socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
}
//监听listenfd
if( listen(listenfd,10) == -1) {
printf(" listen socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
}
printf("====waiting for client's request=======\n");
//accept 和recv,注意接收字符串添加结束符'\0'
while(1)
{
if( (connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1) {
printf(" accpt socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
}
n = recv(connfd,buff,MAXLINE,0);
buff[n] = '\0';
printf("recv msg from client:%s\n",buff);
close(connfd);
}
close(listenfd);
return 0;
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXLINE 4096
int main(int argc, char**argv)
{
int sockfd,n;
char recvline[4096],sendline[4096];
struct sockaddr_in servaddr;
if(argc !=2)
{
printf("usage: ./client <ipaddress>\n");
return 0;
}
//创建socket
if( (sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
//IP地址从“点分十进制”转换到“二进制整肃”
if( inet_pton(AF_INET,argv[1], &servaddr.sin_addr) <=0 ) {
printf("inet_pton error for %s\n",argv[1]);
return 0;
}
//连接
if( connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) <0) {
printf(" connect socket error: %s(errno :%d)\n",strerror(errno),errno);
return 0;
}
printf("send msg to server:\n");
fgets(sendline,4096,stdin);
//send发送
if ( send(sockfd,sendline,strlen(sendline),0) <0) {
printf("send msg error: %s(errno :%d)\n",strerror(errno),errno);
return 0;
}
close(sockfd);
return 0;
}
运行:
g++ server.cpp -o server
g++ client.cpp -o client
分别打开两个终端窗口
一个执行./server命令,
一个执行./client 1227.0.0.1命令。
三、错误记录
在调试过程中发现错误:
socket error: Socket operation on non-socket(errno :88)
经过仔细分析发现:
if( (
sockfd = socket(AF_INET,SOCK_STREAM,0))
== -1)
是优先级出现了问题,这里必须先复制,之后进行关系运算符的比较。
本文风格的代码非常容易在这里犯错误,特此记录。
四、多线程并发服务器
2018年11月3日补充
多线程和多进程的处理方式类似,都是创建一个新的线程,客户端有请求时,用新创建的线程处理。
这里参考文章:多线程服务器,对代码进行模块化封装。
和上文相比,将客户端修改为循环发送,直至输入q
退出
犯的一个错误是服务器printf打印忘记加\n
,导致没有刷新缓冲区,只有客户端每次端开链接时才打印,汗~
//使用pthread线程库
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#define PORT 6666 //服务器端口
#define BACKLOG 5 //listen队列等待的连接数
#define MAXDATASIZE 1024 //缓冲区大小
void process_cli(int connectfd, struct sockaddr_in client); //客户端请求处理函数
void* start_routine(void* arg); //线程函数
typedef struct _ARG {
int connfd;
struct sockaddr_in client;
}ARG; //客户端结构体
void sys_err(const char * ptr_err)
{
perror(ptr_err);
exit(EXIT_FAILURE);
}
//处理客户端链接的接收工作*
void accept_conn(int listenfd)
{
int connectfd; //socket描述符
pthread_t connectthread; //线程体变量
ARG *arg; //客户端结构体变量
struct sockaddr_in client; //客户端地址信息结构体
int sin_size = sizeof(struct sockaddr_in);
while(1) {
//调用accept,返回与服务器连接的客户端描述符
if ((connectfd = accept(listenfd,(struct sockaddr *)&client,(socklen_t *)&sin_size))==-1) {
sys_err("accept() error\n");
}
arg = new ARG;
arg->connfd = connectfd;
memcpy(&arg->client, &client, sizeof(client));
//创建线程,以客户端连接为参数,start_routine为线程执行函数
if (pthread_create(&connectthread, NULL, start_routine, (void*)arg)) {
sys_err("Pthread_create() error");
}
}
}
void tcp_server(uint16_t port)
{
int listenfd; //socket描述符
struct sockaddr_in server; //服务器地址信息结构体
//调用socket,创建监听客户端的socket
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
sys_err("Creating socket failed.");
}
//设置socket属性,端口可以重用
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//初始化服务器地址结构体
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(PORT);
server.sin_addr.s_addr = htonl (INADDR_ANY);
//调用bind,绑定地址和端口
if (bind(listenfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
sys_err("Bind error.");
}
//调用listen,开始监听
if(listen(listenfd,BACKLOG) == -1){
sys_err("listen() error\n");
}
//处理客户端链接的接收工作
accept_conn(listenfd);
//关闭监听socket
close(listenfd);
}
int main()
{
uint16_t port = 6666;
tcp_server(port);
}
void process_cli(int connectfd, sockaddr_in client)
{
int num;
char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
printf("You got a connection from %s. \n",inet_ntoa(client.sin_addr) );
//MSG_WAITALL
while ((num = recv(connectfd, recvbuf, MAXDATASIZE,0)) > 0) {
recvbuf[num] = '\0';
printf("Received size( %d ) message: %s\n",num, recvbuf);
}
/*
num = recv(connectfd, cli_name, MAXDATASIZE,0);
if (num == 0) {
close(connectfd);
return;
}
cli_name[num - 1] = '\0';
printf("Client's content is: %s\n",cli_name);
while (num = recv(connectfd, recvbuf, MAXDATASIZE,0)) {
recvbuf[num] = '\0';
printf("Received client( %s ) message: %s",cli_name, recvbuf);
for (int i = 0; i < num - 1; i++) {
sendbuf[i] = recvbuf[num - i -2];
}
sendbuf[num - 1] = '\0';
send(connectfd,sendbuf,strlen(sendbuf),0);
}
*/
printf("Client disconnected.\n");
close(connectfd);
}
void* start_routine(void* arg)
{
ARG *info = (ARG *)arg;
process_cli(info->connfd, info->client);
//删除对应的堆内存
delete info;
pthread_exit(NULL);
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MAXLINE 4096
int main(int argc, char**argv)
{
int sockfd,n;
char recvline[4096],sendline[4096];
struct sockaddr_in servaddr;
if(argc !=2)
{
printf("usage: ./client <ipaddress>\n");
return 0;
}
//创建socket
if( (sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
printf(" create socket error: %s (errno :%d)\n",strerror(errno),errno);
return 0;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
//IP地址从“点分十进制”转换到“二进制整肃”
if( inet_pton(AF_INET,argv[1], &servaddr.sin_addr) <=0 ) {
printf("inet_pton error for %s\n",argv[1]);
return 0;
}
//连接
if( connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) <0) {
printf(" connect socket error: %s(errno :%d)\n",strerror(errno),errno);
return 0;
}
printf("send msg to server:\n");
while(1)
{
fgets(sendline,sizeof(sendline),stdin);
sendline[strlen(sendline)-1] = '\0';
if(strncasecmp(sendline,"q",1) == 0) {
printf("quit\n");
break;
}
//send发送
// if ( send(sockfd,sendline,strlen(sendline),0) < 0) {
// printf("send msg error: %s(errno :%d)\n",strerror(errno),errno);
// return 0;
// }
int sentbytes = send(sockfd,sendline,strlen(sendline),0);
printf("sent:%d\n",sentbytes);
//bzero(sendline, strlen(sendline));
}
close(sockfd);
return 0;
}