Linux网络编程 3 - 简单的Tcp服务器和客户端编程

linux网络编程,其实指的就是socket编程,下面仅记下自己对socket的理解。

1. socket介绍

    socket又叫套接字,在linux中,一切皆文件,socket其实也就是一种文件描述符。它定义了一组接口,

是处于应用层(比如http、telnet、ftp等)和TCP/IP协议之间的一个抽象层。

常用的socket编程一般分TCP和UDP两种:

    TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的,可靠的,基于字节流的传输

层的协议。UDP(User Datagram Protocol 用户数据报协议)是一种无连接的,不可靠的传输层协议。

    下图要介绍的就是TCP服务器和客户端使用socket建立连接和数据交互的过程:

    TCP编程分为服务端和客户端两种,服务端监听端口,被动等待客户端主动发起连接。

2. socket基本接口介绍

    以下所有函数接口,都可以在linux系统下,使用命令man 函数名,查看函数要引用的头文件、参数列表、

等详细说明。

    2.1 创建socket

        int socket(int domain, int type, int protocol);

        创建socket,其实就是创建一个文件描述符, domain是协议域,又称协议族,常用的有AF_INET、

    AF_INET6等;type是socket的类型,TCP使用SOCK_STREAM,UDP使用SOCK_DGRAM;protocol是

    协议,常用的有IPPROTO_TCP、IPPROTO_UDP,一般填0,会自动选择type类型对应的默认协议。

    2.2 绑定ip地址和端口 bind

        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

        bind函数主要用于服务端,参数sockfd是socket函数的返回值,addr包含了协议族、ip地址、端口。

     addrlen是地址的长度。

    2.3 监听本地地址和端口 listen

        int listen(int sockfd, int backlog);

        只有服务端会使用listen函数,第一个参数是bind之后的socket,backlog是服务器可以缓存的最大

    连接个数,理解listen有一个误区,以为listen之后,就可以等待客户端的连接了,其实真正等待客户端连

    接的是下面要说的accept函数,客户端发起connect之后,当同一时间有多个客户端都发起连接时,如果

    服务器不能及时处理,就会将连接缓存起来,backlog就是缓存队列的最大个数。

    2.4 接受客户端的连接 accept

        int accept(int sockfd, struct sockaddr *client_addr, socklen_t *len);

        服务器监听到客户端connect请求后,使用accept接受请求,参数sockfd是服务器绑定的socket,返回值

    是一个新建的socket,用于和客户端收发数据,同时参数2,3保存了客户端的ip地址和端口。

    2.5 收发数据recv和send

        ssize_t recv(int sockfd, void *buf, size_t len, int flags);

        ssize_t send(int sockfd, const void *buf, size_t len, int flags);

        参数sockfd是accept返回的文件描述符,recv从sockfd的缓冲区中数据复制到buf中,由于TCP是

    基于字节流的,所以每次recv的数据长度和想要接收的长度可能不一致,因此接收数据时,一般都有个

    循环操作,不停地接收数据,直到接收到足够的数据。而send是从buf中向发送缓冲区中写入数据,但是

    可能会出现发送窗口满的情况,所以send一般也会在一个循环中。

 3. 简单的TCP服务端和客户端的C++代码实现

        server阻塞等待客户端的连接,接受连接成功后,循环接收客户端发来的一行消息,并打印到屏幕

#include <stdio.h>
#include <string.h>
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
#include <iostream>
#include <sstream> 
#include <pthread.h>
using namespace std;  

//获取客户端 ip:port 格式的字符串
string getpeeraddrstr(int sockfd)
{
	struct sockaddr_in addr = {0};
	unsigned int size = sizeof(addr);
	getpeername(sockfd, (struct sockaddr*)&addr, &size);
	stringstream ssaddr;
	ssaddr << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
	return ssaddr.str();
}
//TCP服务端主函数
int main()  
{  
    int opt    = 1;   
    int svrfd  = -1;  
    int cltfd  = -1;  
    unsigned short      svrport = 9999;     
    struct sockaddr_in  svraddr = {0};  
    struct sockaddr_in  cltaddr = {0};  
    unsigned int        addrlen = sizeof(cltaddr);  
  
    //创建socket  
    svrfd = socket(AF_INET, SOCK_STREAM, 0);  
    if(-1 == svrfd)  
    {  
        perror("socket failed");  
        return -1;  
    }  
    //设置地址重用  
    setsockopt(svrfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));  
    //绑定ip地址和端口  
    svraddr.sin_family      = AF_INET;  
    svraddr.sin_port        = htons(svrport);     //服务端绑定端口  
    svraddr.sin_addr.s_addr = htonl(INADDR_ANY);  //服务端绑定任意IP  
    if(-1 == bind(svrfd, (struct sockaddr*)&svraddr, sizeof(svraddr)))  
    {  
        perror("bind failed");  
        close(svrfd);  
        return -1;  
    }  
    //开始监听  
    if(-1 == listen(svrfd, 10))  
    {  
        perror("listen failed");  
        close(svrfd);  
        return -1;  
    }  
    while(1)  
    {
    	memset(&cltaddr, 0, sizeof(cltaddr));
        //服务端阻塞等待客户端的连接  
        cltfd = accept(svrfd, (struct sockaddr*)&cltaddr, &addrlen);  
        if(-1 == cltfd)  
        {  
            perror("accept failed");  
            return -1;  
        }
        string straddr = getaddr(cltaddr);
        cout << "connect accept: " << straddr << endl;     
        //客户端连接成功后 服务端接收客户端发来的一行消息 并打印到屏幕  
        while(1)  
        {  
            char buffer[100] = {0};  
            int  recvlen     = 0;  
            recvlen = recv(cltfd, buffer, sizeof(buffer), 0);  
            if(0 < recvlen)  //接收成功  
            {  
                cout << "recv from " << straddr << " " << buffer;  
            }  
            else  
            {  
                cout << "client " << straddr <<" closed" << endl;  
                break;  
            }  
        }  
    }  
} 

  客户端主动发起连接,从命令行接收键盘输入一行信息,并发送给server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
using namespace std;

int main()
{
	int  sock  = -1;
	const char*         svrip   = "0.0.0.0";
	unsigned short      svrport = 9999;   
	struct sockaddr_in  svraddr = {0};


	//创建socket
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sock)
	{
		perror("socket failed");
		return -1;
	}
	svraddr.sin_family      = AF_INET;
	svraddr.sin_port        = htons(svrport);
	svraddr.sin_addr.s_addr = inet_addr(svrip);


	if(-1 == connect(sock, (struct sockaddr*)&svraddr, sizeof(svraddr)))
	{
		perror("connect failed");
		close(sock);
		return -1;
	}
	cout << "connect success" << endl;
	cout << "input: ";
	while(1)
	{
		char c   = getchar();
		int  len = 0;
		if('\n' == c)
		{
			cout << "input: "; 
		}
		len = send(sock, &c, 1, 0);
		if(len < 0)
		{
			break;
		}
	}
	return -1;
}

4. 简单的makefile编写

将上面的服务端和客户端代码分别放在tcpserver.cpp和tcpclient.cpp文件中,并在同一目录下创建makefile文件,

执行如下命令:

make tcpserver 和 ./tcpserver 编译并运行tcpserver

make tcpclient 和 ./tcpclient 编译并运行tcpclient

makefile如下:

.PHONY: tcpserver tcpclient
tcpserver:
	g++ -o tcpserver tcpserver.cpp -Wall -g -lpthread
tcpclient:
	g++ -o tcpclient tcpclient.cpp -Wall -g -lpthread
clean:
	rm -rf tcpserver
	rm -rf tcpclient

猜你喜欢

转载自blog.csdn.net/k117470154/article/details/79184520