Linux实训笔记第三周

前言

道路很远,脚步更长!启航吧,骚年
接上文,继续具体的多线程和网络编程。其实还讲了QT,但大部分人估计都知道,内容实在是繁杂,然而本人又没系统的学过PC端的GUI编程😢,如果以后有时间系统的学了QT,会写点关于QT的笔记,记录PC端GUI编程的经验!这里就不多BB了。

Linux多线程编程

1.编程大体步骤
在 Linux中使用遵循POSIX标准的通用的线程库pthread,具有良好的可移植性。使用头文件<pthread.h>,编译时注意加上额外的编译选项
-lpthread或者-pthread
多线程一般用来处理耗时的操作,比如网络访问,大量计算,搜索等,反正大家都懂,我就介绍一下Linux中线程的特点吧。
(1)通常使用函数 pthread_create来创建线程,并传入任务的函数指针和参数。

NAME
       pthread_create - create a new thread

SYNOPSIS
       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

       Compile and link with -pthread.

(2)创建之后,就开始自动执行相应的任务函数。
(3)在该任务函数运行完之后,线程结束。当前线程自己亦可主动退出,方法是使用函数pthread_exit。

NAME
       pthread_exit - terminate calling thread

SYNOPSIS
       #include <pthread.h>

       void pthread_exit(void *retval);

       Compile and link with -pthread.

(4)线程间相互终止:
pthread_join函数。这个函数是一个线程阻塞的函数,调用它的线程将一直等待到指定的线程结束为止。当函数返回时,表明可以释放已结束线程的相关资源。

NAME
       pthread_join - join with a terminated thread

SYNOPSIS
       #include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);

       Compile and link with -pthread.

pthread_cancel函数。在被取消的线程的内部需要先调用pthread_setcancel函数pthread_setcanceltype函数设置相应的取消状态。

NAME
       pthread_cancel - send a cancellation request to a thread

SYNOPSIS
       #include <pthread.h>

       int pthread_cancel(pthread_t thread);

       Compile and link with -pthread.

(5)线程之间的同步和互斥:Linux采用互斥锁和信号量来保证原子操作。只介绍互斥锁。
互斥锁:互斥锁只有两种状态:上锁和解锁。可以把互斥锁看成某种意义上的全局变量。==同一时刻只能有一个线程持有某个互斥锁。==能够对共享资源进行操作。若没有互斥锁的线程对一个已上锁的互斥锁加锁,该线程就会睡眠,直到其他线程释放互斥锁为止。
互斥锁机制的基本函数如下。
互斥锁初始化:pthread_mutex_init()
互斥锁上锁:pthread_mutex_lock()
互斥锁判断上锁:pthread_mutex_trylock()
互斥锁解锁:pthread_mutex_unlock()
消除互斥锁:pthread_mutex_destory()

NAME
       pthread_mutex_init,      pthread_mutex_lock,     pthread_mutex_trylock,
       pthread_mutex_unlock, pthread_mutex_destroy - operations on mutexes

SYNOPSIS
       #include <pthread.h>

       pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;

       pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

       pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

       int  pthread_mutex_init(pthread_mutex_t  *mutex,  const  pthread_mutex‐
       attr_t *mutexattr);

       int pthread_mutex_lock(pthread_mutex_t *mutex);

       int pthread_mutex_trylock(pthread_mutex_t *mutex);

       int pthread_mutex_unlock(pthread_mutex_t *mutex);
       
	   int pthread_mutex_destroy(pthread_mutex_t *mutex);

(6)总体而言,使用多线程没啥技术难度,就是要保证同步资源的访问要有序。其他特点的就和其他平台的线程编程一样,但是大部分人用惯了已经封装好的线程接口,估计有点不适应这么原始的线程技术,哈哈,不赘述了。
2.一个简单例子,多线程拷贝大文件

头文件

#ifndef DAY8_H_INCLUDED
#define DAY8_H_INCLUDED

#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

typedef struct {
  int threadNo;
  int start;
  int end;
  char* sourceFile;
  char* destFile;
}taskInfo;

void copyBigFile();
#endif // DAY8_H_INCLUDED

.c文件

#include "day8.h"
const int eachSize=1024*1024*30;//每个线程负责30MB的大小
const int maxThreadNum=20;//最多拷贝30*20M的文件
int finishSize=0;//记录完成的进度
pthread_mutex_t mutex;//互斥锁

/*! \brief 记录进度
 *  \param hasRead:线程当前进度
 *
 *return void
 */
void sumCounter(int hasRead)
{
    pthread_mutex_lock(&mutex);
    finishSize+=hasRead;
    pthread_mutex_unlock(&mutex);

}
/*! \brief任务函数
 *  \param info:存放参数的结构体地址
 *
 *return void
 */
void* fileCopy(void* info)
{
    int fd_s=-1;
    int fd_d=-1;
    char buff[256]= {0};
    int totalSize=0;
    int hasRead=0;
    int readSum=0;
    totalSize=((taskInfo*)info)->end - ((taskInfo*)info)->start;

    //打开源文件和目标文件
    fd_s=open(((taskInfo*)info)->sourceFile,O_RDONLY);
    fd_d=open(((taskInfo*)info)->destFile,O_RDWR);

    //移动文件指针到指定位置
    if(-1==lseek(fd_s, ((taskInfo*)info)->start,SEEK_SET))
    {
        pthread_exit(NULL);
    }
    if(-1==lseek(fd_d, ((taskInfo*)info)->threadNo * eachSize,SEEK_SET))
    {
        pthread_exit(NULL);
    }

    while(1)
    {
        if((totalSize-readSum-sizeof(buff))>0)//还可以读满一次缓冲
        {
            hasRead=read(fd_s,buff,sizeof(buff));
        }
        else
        {
            hasRead=read(fd_s,buff,(totalSize - readSum));
        }
        write(fd_d,buff,hasRead);
        memset(buff,0,hasRead);
        readSum+=hasRead;
        sumCounter(hasRead);//记录已经写入字节
        if(readSum==totalSize)
        {
            close(fd_s);
            close(fd_d);
            printf("线程%d 文件拷贝完成\n",((taskInfo*)info)->threadNo);
            return;
        }
    }
}

/*! \brief 多线程拷贝大文件
 *  \param void
 *
 *return void
 */
void copyBigFile()
{
    char sourceFile[256]= {0};
    char destFile[256]= {0};
    char inputStr[256]= {0};
    int fd_s=-1;
    int fd_d=-1;
    struct stat fileInfo;//存放文件信息
    printf("请输入当前目录下已存在的文件名,长度小于256!\n");
    fgets(inputStr,256,stdin);
    strncpy(sourceFile,inputStr,strlen(inputStr)-1);
    fd_s=open(sourceFile,O_RDWR);
    while(-1==fd_s)
    {
        perror("文件不存在,请重新输入文件名\n");
        fgets(inputStr,256,stdin);
        strncpy(sourceFile,inputStr,strlen(inputStr)-1);
        fd_s=open(sourceFile,O_RDWR);
    }
    if(fstat(fd_s,&fileInfo)<0)
    {
        perror("获取文件信息失败\n");
        exit(-1);
    }
    int totalSize=fileInfo.st_size;//获取文件总大小
    int threadNum=totalSize/eachSize;//计算最多需要几个线程

    printf("请输入新的文件名\n");
    fgets(inputStr,256,stdin);
    strncpy(destFile,inputStr,strlen(inputStr)-1);
    fd_d=open(destFile,O_RDWR|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR);
    if(-1==fd_d)
    {
        perror("未知原因,新文件创建失败,退出!");
        return;
    }

    //主线程关闭文件
    close(fd_s);
    close(fd_d);

    //分配线程
    int result=0;
    pthread_t tid[maxThreadNum];
    taskInfo info[threadNum];
    pthread_mutex_init(&mutex,NULL);
    for(int i=0; i<threadNum+1; i++)
    {
        info[i].threadNo=i;
        info[i].start=i*eachSize;
        info[i].end=i*eachSize+eachSize;
        info[i].sourceFile=sourceFile;
        info[i].destFile=destFile;

        result=pthread_create(&tid[i],NULL,fileCopy,(void*)(&(info[i])));
        if(result<0)
        {
            perror("create thread error");
        }
        result=pthread_detach(tid[i]);
        if(result!=0)
        {
            strerror(result);

        }
    }

    //显示当前进度
    while(1)
    {
        sleep(1);
        if(finishSize<totalSize)
        {
            printf("当前拷贝完成了%f %\n",finishSize*1.0/totalSize*100);
        }
        else
        {
            printf("拷贝完成了!!!!!!\n");
            return;
        }
    }
}

来拷贝一个300多MB的PDF看一下:

请输入当前目录下已存在的文件名,长度小于256!
1.pdf
请输入新的文件名
2.pdf
当前拷贝完成了16.288223 %
当前拷贝完成了29.915408 %
当前拷贝完成了43.936952 %
当前拷贝完成了58.419156 %
当前拷贝完成了72.685684 %
当前拷贝完成了86.582927 %
线程10 文件拷贝完成
线程11 文件拷贝完成
线程9 文件拷贝完成
当前拷贝完成了93.543505 %
线程7 文件拷贝完成
当前拷贝完成了96.320397 %
线程1 文件拷贝完成
线程3 文件拷贝完成
线程2 文件拷贝完成
线程8 文件拷贝完成
线程6 文件拷贝完成
线程0 文件拷贝完成
线程5 文件拷贝完成
当前拷贝完成了99.996224 %
线程4 文件拷贝完成
拷贝完成了!!!!!!

Process returned 0 (0x0)   execution time : 17.024 s
Press ENTER to continue.

嗯,大威天龙,雕虫小技!😂

Linux网络基础编程

一、
1.网络体系结构
(1) OSI 模型和 TCP/IP模型
网络体系结构指的是网络的分层结构以及每层使用的协议的集合。
OSI模型:它是基于国际标准化组织(ISO)的建议发展来的,它分为7个层次:应用层、表示层、会话层、传输层、网络层、数据链路层及物理层。这是参考模型,记住,只是参考!

OSI参考模型
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层

TCP/IP模型:将OSI的7层协议模型简化为4层,从而更有利于实现和高效通信。

TCP/IP模型
应用层
传输层
网络层
网络接口层

TCP/IP 是一个复杂的协议族,是由一组专业化协议组成的。这些协议|包括IP、TCP、UDP、ARP、ICMP以及其他的一些被称为子协议的协议。
(2)各个层功能特点
网络接口层:是TCP/IP的最底层,负责将二进制流转换为数据帧,并进行数据帧的发送和接收。

网络层:负责在主机之间的通信中选择数据包的传输路径,即路由。

传输层:负责实现应用程序之间的通信服务,这通信又称为端到端通信。传输层要系统地管理信息的流动,还要提供可靠的传输服务,以确保数据到达无差错、无乱序。传输层协议软件把要传输的数据流划分为分组,把每个分组连同目的地址交给网络层去发送。

应用层:是分层模型的最高层。应用程序使用相应的应用层协议,把封装好的数据提交给传输层或是从传输层接收数据并处理。

2.TCP/IP模型特点
(1)TCP/IP 模型边界特性
TCP/IP分层模型中有两大边界特性:一个是地址边界特性,它将IP逻辑地址与底层网络的硬件地址分开;另一个是操作系统边界特性,它将网络应用与协议软件分开。

(2)IP层特性
IP层作为通信子网的最高层,提供无连接的数据包传输机制,但IP协议并不能保证IP包传递的可靠性。TCP/IP的重要思想之一就是通过IP将各种底层网络技术统一起来,达到屏蔽底层细节,提供统一虚拟网的目的。

(3)TCP/IP的可靠性特性
在TCP/IP网络中,IP采用无连接的数据包机制,即只管将数据包尽力传送到目的主机,无论传输正确与否,不做验证,不发确认,也不保证数据包的顺序。TCP/IP的可靠性体现在传输层协议之一的TCP。TCP提供面向连接的服务,因为传输层是端到端的,所以TCP/IP的可靠性被称为端到端可靠性。

综上可知,TCP/IP的特点就是将不同的底层物理网络、拓扑结构隐藏起来,向用户和应用程序提供通用、统一的网络服务。这样,从用户的角度看,整个TCP/IP网络就是一个统一的整体,它独立于具体的各种物理网络技术,能够向用户提供一个通用的网络服务。

3 .TCP和 UDP
TCP/IP协议群中的核心协议被设计运行在网络层和传输层,它们为网络中的各主机提供通信服务,也为模型的最高层——应用层中的协议提供服务。
TCP:
(1)概述
TCP向应用层提供可靠的面向连接的数据流传输服务。它能提供高可靠通信(即数据无误、数据无丢失、数据无失序、数据无重复到达)。
强过源/目的IP可以唯一地区分网络中的两个设备,再通过源/目的端口可以区分网络中两个通信的应用程序。

(2)3次握手协议。
TCP是面向连接的协议。所谓面向连接,就是当计算机双方通信时必须先建立连接,然后进行数据通信,最后关闭连接。TCP在建立连接时包括3个步骤:

第一步(A→B):主机A(客户端)向主机B(服务器端)发送一个包含SYN(同步,syn=j)标志的TCP报文,并进入 SYN_SEND状态,等待服务器确认。

第二步(B→A):主机 B在收到客户端的SYN报文后,将返回一个SYN+ACK(ack=j+1,syn=k)的报文,表示主机B的SYN被确认,此时服务器进入SYN_RECV状态。

第三步(A→B):客户端A收到服务器的SYN+ACK报文后,向服务器发送确认ACK(ack=k+1)报文,客户端和服务器端进入ESTABLISHED状态,完成TCP连接。

(3)TCP数据包头:自己找资料看吧,对普通人来说,没啥必要记住。

UDP
(1)概述
UDP即用户数据报协议,是一种面向无连接的不可靠传输协议,具有资源消耗少,速度快的特点。
(2)UDP数据包头:再说一遍,对普通人而言,TCP和UDP的数据包头没必要记住,你也用不到,但抓包测试的时候又是必须看懂的,自己量力而记吧。但是http的包头结构应该是常识。
(3)协议的选择
协议的选择应该考虑到数据可靠性、应用的实时性和网络的可靠性。

二、网络基础编程

1.(1)套接字概述
对于应用开发人员来说,套接字就是一种特殊的I/0接口,也是一种文件描述符。每一个Socket都可用网络地址结构{协议、本地地址、本地端口}来表示。 Socket 通过一个专门的函数创建,并返回一个整型的Socket描述符。随后的各种操作都是通过 Socket描述符来实现的。

(2)套接字类型
常见的Socket类型有如下3种:

①流式套接字
流式套接字提供可靠的、面向连接的通信流,保证数据传输的可靠性按序收发。TCP通信使用的就是流式套接字。
②数据报套接字
数据报套接字实现了一种不可靠、无连接的服务。数据通过相互独立的数据报套接字。报文进行传输,是无序的,并且不保证可靠的传输。
③原始套接字
允许对底层协议(如IP或ICMP)进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

2 IP地址
(1)IP地址的作用
IP地址用来标识网络中的一台主机。根据不同的协议版本,分为Ipv4(32位)和Ipv6(128位)。一个IP地址包含两部分:网络号和主机号。其中,网络号和主机号根据子网掩码来区分。

(2)IP地址格式转换
IP地址有两种不同格式:十进制点分形式和32位二进制形式。前者是用户所熟悉的形式,而后者则是网络传输中IP地址的存储方式。所以需要把熟知的点分十进制转化为32位二进制形式!
IPv4地址转换函数有inet_aton()、inet_addr()和 inet_ntoa(),而IPv4和 IPv6兼容的函数有inet_pton()和
inet_ntop()。inet_addr和 inet_ptons函数是将十进制点分形式转换为二进制形式,而 inet_ntop()是 inet_pton()的反向操作,将二进制地址形式转换为十进制点分形式。自己看形参列表的类型就知道搞啥的,比如形参是 const char *c ,返回值是 in_addr_t ,这就是要点分十进制转化为32位二进制。

NAME
       inet_aton,    inet_addr,    inet_network,   inet_ntoa,   inet_makeaddr,
       inet_lnaof, inet_netof - Internet address manipulation routines

SYNOPSIS
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>

       int inet_aton(const char *cp, struct in_addr *inp);

       in_addr_t inet_addr(const char *cp);

       in_addr_t inet_network(const char *cp);

       char *inet_ntoa(struct in_addr in);

       struct in_addr inet_makeaddr(int net, int host);

       in_addr_t inet_lnaof(struct in_addr in);

       in_addr_t inet_netof(struct in_addr in);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       inet_aton(), inet_ntoa(): _BSD_SOURCE || _SVID_SOURCE

3.端口和字节序
(1)端口是无符号短整型,取值0到65535,其中系统占用0到1023。
(2)TCP和UDP端口号独立,互不影响。
(3)字节序又称为主机字节序,是指计算机中多字节整型数据的存储方式。字节序有两种:大端(高位字节存储在低位地址,低位字节存储往高位地址)和小端(和大端序相反,PC 通常采用小端模式)。在网络通信中,发送方和接收方有可能使用不同的字节序,为了保证数据接收后被正确地解析处理,统一规定;数据以高位字节优先顺字在网络中传输。因此数据在发送前和接收后都需要在主机字节序和网络字节序之间转换。
字节序转换涉及4个函数:htons()、ntohs()、htonl()和ntohl()。这的h代表 host,n代表 network,s代表short,l代表 long。通常16位的IP端口号用前两个函数处理,而IP地址用后两个函数来转换。

NAME
       htonl,  htons,  ntohl,  ntohs - convert values between host and network
       byte order

SYNOPSIS
       #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

4.TCP编程

服务器端和客户端使用TCP的流程图:

服务器端客户端
socketsocket
bind-
listenbind
acceptconnect
recv/recvfromsend/sendto
send/sendtorecv/recvfrom)
closeclose

结合流程图具体说明。
(1)socket():该函数用于创建一个套接字,同时指定协议和类型。

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

(2 )bind():该函数将保存在相应地址结构中的地址信息与套接字进行绑定。它主要用于服务器端,客户端创建的套接字可以不绑定地址。

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

(3) listen():在服务器端程序成功建立套接字并与地址进行绑定之后,通过调用listen()函数将套接字设置成监听模式(被动模式),准备接收客户端的连接请求。

NAME
       listen - listen for connections on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

(4)accept():服务器端通过调用accept()函数等待并接收客户端的连接请求。建立好TCP连接后,该函数会返回一个新的已连接套接字。

NAME
       accept - accept a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sys/socket.h>

       int accept4(int sockfd, struct sockaddr *addr,
                   socklen_t *addrlen, int flags);

(5)connect():客户端通过该函数向服务器端的监听套接字发送连接请求。

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

(6)send()和 recv():这两个函数通常在TCP通信过程中用于发送和接收数据,也可以用在UDP中。

NAME
       send, sendto, sendmsg - send a message on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

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

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

(7) sendto()和recvfrom():这两个函数一般在UDP通信过程中用于发送和接收数据。当用在TCP时,后面的几个与地址有关的参数不起作用,函数作用等同于send()和 recv()。

NAME
       recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

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

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

5.来个简单的C/S demo
service端:

#include "day9.h"

/*! \brief 服务器端初始化
 *  \param void
 *
 *return void
 */
void servInit()
{
    int listenfd,connfd;
    struct sockaddr_in servaddr,cliaddr;
    socklen_t peerlen;
    //1.建立socket连接
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        perror("socket create fail...\n");
        exit(-1);
    }
    printf("listenfd=%d...\n",listenfd);
    //2.设置地址结构体的参数
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(8080);
    servaddr.sin_addr.s_addr=INADDR_ANY;//系统自动给ip地址
    //3.绑定函数:将地址信息与套接字进行绑定
    if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
    {
        perror("socket bind fail...\n");
        exit(-1);
    }
    printf("bind success...\n");
    //4.调用listen
    if(listen(listenfd,10)==-1)
    {
        perror("socket listen fail...\n");
        exit(-1);
    }
    printf("listening...\n");
    //5.调用accept,等待客服端连接
    peerlen=sizeof(cliaddr);
    while(1)
    {
        //accept是阻塞函数
        if((connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&peerlen))<0)
        {
            perror("socket accept fail...\n");
            exit(-1);
        }
        //6.进入具体的消息收发
        return servRT(listenfd,connfd);
    }
}

/*! \brief 具体的通信内容:简单的收发,收到 Q 断开连接
 *  \param listenfd:监听的套接字,connfd:建立连接通信的套接字
 *
 *return void
 */
void servRT(int listenfd,int connfd)
{
    char buff[BUFF_SIZE];
    while(1)
    {
      memset(buff,0,sizeof(buff));
    if(recv(connfd,buff,sizeof(buff),0)==-1)
    {
        perror("socket recv fail...\n");
        exit(-1);
    }
    if('Q'==buff[0])
    {
    stpcpy(buff,"886!\n");
    send(connfd,buff,sizeof(buff),0);
    close(connfd);
    close(listenfd);
    exit(0);
    }
    printf("recv:%s",buff);
    stpcpy(buff,"Serv Received!\n");
    send(connfd,buff,sizeof(buff),0);
    }

}


client端

#include "day10.h"

/*! \brief 客服端初始化
 *  \param void
 *
 *return void
 */
void clientInit()
{
    int sockfd;
    struct sockaddr_in servaddr;
    //1.建立socket连接
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        perror("socket create fail...\n");
        exit(-1);
    }
    printf("sockfd=%d...\n",sockfd);
    //2.设置地址结构体的参数
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(8080);
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//本机ip地址
    //3.connect函数
    if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
    {
        perror("socket connect fail...\n");
        exit(-1);
    }
    printf("connect success...\n");
    return clientRT(sockfd);

}
/*! \brief  具体的收发消息
 *  \param  sockfd:已连接的套接字
 *
 *return  void
 */
void clientRT(int sockfd)
{
    char buff[BUFF_SIZE]="hello!";
    //4.调用send
    send(sockfd,buff,sizeof(buff),0);
    memset(buff,0,sizeof(buff));
    while(1)
    {
        printf("请输入要发送的内容(按Q+回车退出):\n");
        memset(buff,0,sizeof(buff));
        fgets(buff,sizeof(buff),stdin);
        if('Q'==buff[0])
        {
          printf("虽然你按Q了,但还需要再点一次回车刷新输出,才能结束!\n");
        }
        send(sockfd,buff,sizeof(buff),0);
        if(recv(sockfd,buff,sizeof(buff),0)==-1)
        {
            perror("socket recv fail...\n");
            exit(-1);
        }
        if(strcmp("886!\n",buff)==0)
       {
          printf("收到退出消息,退出!\n");
          close(sockfd);
          exit(0);
       }
        printf("recv:%s\n",buff);
    }
}


测试结果如下:代码其实不算完美,消息的接收和发送都应该放在线程中操作,自己完善吧!
在这里插入图片描述
6.UDP编程

使用 UDP协议通信时服务器端和客户端无需建立接,只要知道对方套接字的地址信息,就可以发送数据。 服务器端只需创建一个套接字用于接收不同客户端发来请求,经过处理后再把结果发送给对应的客户端。
服务器端和客户端使用UDP的流程图

服务器端客户端
socketsocket
listen-
acceptconnect
recv/recvfromsend/sendto
send/sendtorecv/recvfrom)
closeclose

相比TCP编程少了几步,反而简单,demo就不用了吧,参照TCP,自己去悟吧!

三、 服务器模型
在网络通信过程中,服务器端通常需要处理多个客户端。由于多个客户端的请求可能会同时到来,服务器端可采用不同的方法来处理。总体上来说,服务器端可采用两种模型来实现:循环服务器模型和并发服务器模型。

循环服务器模型是指服务器端依次处理每个客户端,直到当前客户端的所有请求处理完毕,再处理下一个客户端。这类模型的优点是简单,缺点显而易见。特别是TCP循环服务器模型,由于必须先处理完当前客户端,因此容易造成其他客户端等待时间过长的情况。

为了提高服务器的并发处理能力,又引入了并发服务器模型。其基本思想是在服务器端采用多任务机制(多进程或多线程),分别为每个客户端创建一个任务来处理,极大地提高了服务器的并发处理能力。

1.循环服务器
运行介绍
(1)服务器端从连接请求队列中提取请求,建立连接并返回新的已连接套接字。
(2)服务器端通过已连接套接字循环接收数据,处理并发送给客户端,直到客服端关闭连接。
(3)服务器端关闭已连接套接字,返回步骤(1)。
特点:采用这种模型的服务器端无法同时为多个客户端服务。
2. 并发服务器
运行介绍
TCP并发服务器模型在网络通信中被广泛使用,既可以采用多进程也可以采用多线程来实现。以多进程为例 其工作流程如下。
(1)服务器端父进程从连接请求队列中提取请求。确立连接并返回新的已连接套接字。
(2)服务器端父进程创建子进程为客户端服务。客户关闭连接时,子进程结束。
(3)服务器端父进程关闭已连接套接字,返回步骤(1)。
特点:
(1)每个客户端在服务器端有一个专门的子进程为其服务。
(2)服务器端的多个子进程同时运行(宏观上),处理多个客户端。
(3)服务器端的父进程不具体处理每个客户端的数据请求。
采用这种模型的服务器端需要避免僵死进程。
demo就不写了,自己有时间可以练一下,就是加入了线程和进程而已,不难。

OK!
第三周笔记到此结束,时间过得好快啊!!!!学习就是逆水行舟,不进则退!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖工人_0803号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值