Linux下TCP/IP编程--TCP实战

本文参考自徐晓鑫《后台开发》,重点复习总结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;
}

  • 16
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值