2017-2018-1 20155202 《信息安全系統設計基礎》第14周學習總結

2017-2018-1 20155202 《信息安全系統設計基礎》第14周學習總結

==深刻理解第11章:網絡編程==:

網絡編程背景:

  • Internet的應用範圍由最先的軍事、國防,擴展到美國國內的學術機構,進而迅速覆蓋了全球的各個領域,運營性質也由科研、教育爲主逐漸轉向商業化。
  • 在科學研究中,常常碰到「種瓜得豆」的事情,Internet的出現也正是如此:它的原型是1969年美國國防部遠景研究規劃局(Advanced Research Projects Agency)爲軍事實驗用而創建的網絡,名爲ARPANET,初期只有四臺主機,其設計目標是當網絡中的一部分因戰爭緣由遭到破 壞時,其他部分仍能正常運行;80年代初期ARPA和美國國防部通訊局研製成功用於異構網絡的TCP/IP協議並投入使用;1986年在美國國會科學基金會(National Science Foundation)的支持下,用高速通訊線路把 分佈在各地的一些超級計算機鏈接起來,以NFSNET接替ARPANET;進而又通過十幾年的發展造成Internet。
  • 90年代初,中國做爲第71個國家級網加入Internet,我國已經開放了Internet,經過中國公用互連網絡(CHINANET)或中國教育科研計算機網(CERNET)均可與Internet聯通。只要有一臺微機,一部調制解調器和一部國內直撥電話就可以很方便地享受到Internet的資源;這是Internet逐步"爬"入普通人家的緣由之一;緣由之二,友好的用戶界面、豐富的信息資源、貼近生活的人情化感覺使非專業的家庭用戶既作到應用自如,又能大飽眼福,甚至利用它爲本身的工做、學習、生活錦上添花,真正作到"足不出戶,可成就天下事,瀟灑做當代人"。
  • 網絡的神奇做用吸引着愈來愈多的用戶加入其中,正因如此,網絡的承受能力也面臨着愈來愈嚴峻的考驗―從硬件上、軟件上、所用標準上......,各項技術都須要適時應勢,對應發展,這正是網絡迅速走向進步的催化劑。到了今天,Internet可以負擔如此衆多用戶的參與,說明咱們的網絡技術已經成長到了至關成熟的地步,用戶本身也能耳聞目擊不斷涌現的新名詞、新概念。但這還不是終結,僅僅是歷史長河的一段新紀元的開始而已。

客戶端一服務器編程模型

每一個網絡應用都是基於客戶端一服務器模型的
採用這個模型,一個應用是由一個服務器進程和一個或者多個客戶端進程組成。服務器管理某種資源,而且經過操做這種資源來爲它的客戶端提供某種服務。例如,一個Web服務器管理着一組磁盤文件,它會表明客戶端進行檢索和執行。一個FTP服務器管理着一組磁盤文件,它會爲客戶端進行存儲和檢索。類似地,一個電子郵件服務器管理着一些文件,它爲客戶端進行讀和更新。客戶端一服務器模型中的基本操做是事務(transaction)。一個客戶端一服務器事務由如下四步組成。
  • 1)當一個客戶端須要服務時,它向服務器發送一個請求,發起一個事務。例如,當Web瀏覽器須要一個文件時,它就發送一個請求給Web服務器。
  • 2)服務器收到請求後,解釋它,並以適當的方式操做它的資源。例如,當Web服務 器收到瀏覽器發出的請求後,它就讀一個磁盤文件。
  • 3)服務器給客戶端發送一個響應,並等待下一個請求。例如,Web服務器將文件發送回客戶端。
  • 4)客戶端收到響應並處理它。例如,當Web瀏覽器收到來自服務器的一頁後,就在
    屏幕上顯示此頁。

==socket編程:==

==一、網絡中進程之間如何通訊?==

本地的進程間通訊(IPC)有不少種方式,但能夠總結爲下面4類:html

  1. 消息傳遞(管道、FIFO、消息隊列)
  2. 同步(互斥量、條件變量、讀寫鎖、文件和寫記錄鎖、信號量)
  3. 共享內存(匿名的和具名的)
  4. 遠程過程調用(Solaris門和Sun RPC)
  • 網絡中進程之間如何通訊?首要解決的問題是如何惟一標識一個進程,不然通訊無從談起!在本地能夠經過進程PID來惟一標識一個進程,可是在網絡中這是行不通的。其實TCP/IP協議族已經幫咱們解決了這個問題,網絡層的「ip地址」能夠惟一標識網絡中的主機,而傳輸層的「協議+端口」能夠惟一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就能夠標識網絡的進程了,網絡中的進程通訊就能夠利用這個標誌與其它進程進行交互。linux

  • 使用TCP/IP協議的應用程序一般採用應用編程接口:UNIXBSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通訊。就目前而言,幾乎全部的應用程序都是採用socket,而如今又是網絡時代,網絡中進程通訊是無處不在,這就是我爲何說「一切皆socket」。git

==二、什麼是Socket?==

  • [ ] 道網絡中的進程是經過socket來通訊的,那什麼是socket呢?socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,均可以用「打開open –> 讀寫write/read –> 關閉close」模式來操做。個人理解就是Socket就是該模式的一個實現,socket便是一種特殊的文件,一些socket函數就是對其進行的操做(讀/寫IO、打開、關閉)

==3.一、socket()函數==

web

int socket(int domain, int type, int protocol);
  • socket函數對應於普通文件的打開操做。普通文件的打開操做返回一個文件描述字,而socket()用於建立一個socket描述符(socket descriptor)
    ,它惟一標識一個socket。這個socket描述字跟文件描述字同樣,後續的操做都有用到它,把它做爲參數,經過它來進行一些讀寫操做。編程

  • 正如能夠給fopen的傳入不一樣參數值,以打開不一樣的文件。建立socket的時候,也能夠指定不一樣的參數建立不一樣的socket描述符,socket函數的三個參數分別爲:瀏覽器

  1. domain:即協議域,又稱爲協議族(family)。經常使用的協議族有,AF_INET、AF_INET六、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名做爲地址。安全

  2. type:指定socket類型。經常使用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的類型有哪些?)。服務器

  3. protocol:故名思意,就是指定協議。經常使用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議(這個協議我將會單獨開篇討論!)。網絡

  • 注意:並非上面的type和protocol能夠隨意組合的,如SOCK_STREAM不能夠跟IPPROTO_UDP組合。當protocol爲0時,會自動選擇type類型對應的默認協議。多線程

  • 當咱們調用socket建立一個socket時,返回的socket描述字它存在於協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。若是想要給它賦值一個地址,就必須調用bind()函數,不然就當調用connect()、listen()時系統會自動隨機分配一個端口。
    3.二、bind()函數
    正如上面所說bind()函數把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數的三個參數分別爲:

sockfd:即socket描述字,它是經過socket()函數建立了,惟一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。
addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址建立socket時的地址協議族的不一樣而不一樣,如ipv4對應的是:

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
ipv6對應的是: 
struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};

struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};
Unix域對應的是: 
#define UNIX_PATH_MAX    108

struct sockaddr_un { 
    sa_family_t sun_family;               /* AF_UNIX */ 
    char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
};

addrlen:對應的是地址的長度。

  • 一般服務器在啓動的時候都會綁定一個衆所周知的地址(如ip地址+端口號),用於提供服務,客戶就能夠經過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是爲何一般服務器端在listen以前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

網絡字節序與主機字節序

  • 主機字節序就是咱們日常說的大端和小端模式:不一樣的CPU有不一樣的字節序類型,這些字節序是指整數在內存中保存的順序,這個叫作主機序。引用標準的Big-Endian和Little-Endian的定義以下:
a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
  b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
  • 網絡字節序:4個字節的32 bit值如下面的次序傳輸:首先是0~7bit,其次8~15bit,而後16~23bit,最後是24~31bit。這種傳輸次序稱做大端字節序。因爲TCP/IP首部中全部的二進制整數在網絡中傳輸時都要求以這種次序,所以它又稱做網絡字節序。字節序,顧名思義字節的順序,就是大於一個字節類型的數據在內存中的存放順序,一個字節的數據沒有順序的問題了。
  • 因此:在將一個地址綁定到socket的時候,請先將主機字節序轉換成爲網絡字節序,而不要假定主機字節序跟網絡字節序同樣使用的是Big-Endian。因爲這個問題曾引起過血案!公司項目代碼中因爲存在這個問題,致使了不少莫名其妙的問題,因此請謹記對主機字節序不要作任何假定,務必將其轉化爲網絡字節序再賦給socket。

    listen()、connect()函數

  • 若是做爲一個服務器,在調用socket()、bind()以後就會調用listen()來監聽這個socket,若是客戶端這時調用connect()發出鏈接請求,服務器端就會接收到這個請求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • listen函數的第一個參數即爲要監聽的socket描述字,第二個參數爲相應socket能夠
    排隊的最大鏈接個數。socket()函數建立的socket默認是一個主動類型的,listen函數將socket變爲被動類型的,等待客戶的鏈接請求。

  • connect函數的第一個參數即爲客戶端的socket描述字,第二參數爲服務器的socket地址,第三個參數爲socket地址的長度。客戶端經過調用connect函數來創建與TCP服務器的鏈接。

accept()函數

  • TCP服務器端依次調用socket()、bind()、listen()以後,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()以後就想TCP服務器發送了一個鏈接請求。TCP服務器監聽到這個請求以後,就會調用accept()函數取接收請求,這樣鏈接就創建好了。以後就能夠開始網絡I/O操做了,即類同於普通文件的讀寫I/O操做。

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

  • accept函數的第一個參數爲服務器的socket描述字,第二個參數爲指向struct sockaddr *的指針,用於返回客戶端的協議地址,第三個參數爲協議地址的長度。若是accpet成功,那麼其返回值是由內核自動生成的一個全新的描述字,表明與返回客戶的TCP鏈接。

  • 注意:accept的第一個參數爲服務器的socket描述字,是服務器開始調用socket()函數生成的,稱爲監聽socket描述字;而accept函數返回的是已鏈接的socket描述字。一個服務器一般一般僅僅只建立一個監聽socket描述字,它在該服務器的生命週期內一直存在。內核爲每一個由服務器進程接受的客戶鏈接建立了一個已鏈接socket描述字,當服務器完成了對某個客戶的服務,相應的已鏈接socket描述字就被關閉。

read()、write()等函數

萬事具有隻欠東風,至此服務器與客戶已經創建好鏈接了。能夠調用網絡I/O進行讀寫操做了,即實現了網咯中不一樣進程之間的通訊!網絡I/O操做有下面幾組:

  1. read()/write()
  2. recv()/send()
  3. readv()/writev()
  4. recvmsg()/sendmsg()
  5. recvfrom()/sendto()
  • 我推薦使用recvmsg()/sendmsg()函數,這兩個函數是最通用的I/O函數,實際上能夠把上面的其它函數都替換成這兩個函數。它們的聲明以下:
#include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);
       ssize_t write(int fd, const void *buf, size_t count);

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

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t recv(int sockfd, 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 recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

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

read函數

  • 是負責從fd中讀取內容.當讀成功時,read返回實際所讀的字節數,若是返回的值是0表示已經讀到文件的結束了,小於0表示出現了錯誤。若是錯誤爲EINTR說明讀是由中斷引發的,若是是ECONNREST表示網絡鏈接出了問題。

write函數

  • 將buf中的nbytes字節內容寫入文件描述符fd.成功時返回寫的字節數。失敗時返回-1,並設置errno變量。 在網絡程序中,當咱們向套接字文件描述符寫時有倆種可能。1)write的返回值大於0,表示寫了部分或者是所有的數據。2)返回的值小於0,此時出現了錯誤。咱們要根據錯誤類型來處理。若是錯誤爲EINTR表示在寫的時候出現了中斷錯誤。若是爲EPIPE表示網絡鏈接出現了問題(對方已經關閉了鏈接)。

  • 其它的我就不一一介紹這幾對I/O函數了,具體參見man文檔或者baidu、Google,下面的例子中將使用到send/recv。

close()函數

在服務器與客戶端創建鏈接以後,會進行一些讀寫操做,完成了讀寫操做就要關閉相應的socket描述字,比如操做完打開的文件要調用fclose關閉打開的文件。

#include <unistd.h>
int close(int fd);
  • close一個TCP socket的缺省行爲時把該socket標記爲以關閉,而後當即返回到調用進程。該描述字不能再由調用進程使用,也就是說不能再做爲read或write的第一個參數。

  • 注意:close操做只是使相應socket描述字的引用計數-1,只有當引用計數爲0的時候,纔會觸發TCP客戶端向服務器發送終止鏈接請求。

socket中TCP的三次握手創建鏈接詳解

咱們知道tcp創建鏈接要進行「三次握手」,即交換三個分組。大體流程以下:

  1. 客戶端向服務器發送一個SYN J
    服務器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
    客戶端再想服務器發一個確認ACK K+1
    只有就完了三次握手,可是這個三次握手發生在socket的那幾個函數中呢?請看下圖:

socket中發送的TCP三次握手

  • [x] 從圖中能夠看出,當客戶端調用connect時,觸發了鏈接請求,向服務器發送了SYN J包,這時connect進入阻塞狀態;服務器監聽到鏈接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到服務器的SYN K ,ACK J+1以後,這時connect返回,並對SYN K進行確認;服務器收到ACK K+1時,accept返回,至此三次握手完畢,鏈接創建。

  • 總結:客戶端的connect在三次握手的第二個次返回,而服務器端的accept在三次握手的第三次返回。

    socket中TCP的四次握手釋放鏈接詳解

  • 上面介紹了socket中TCP的三次握手創建過程,及其涉及的socket函數。如今咱們介紹socket中的四次握手釋放鏈接的過程,請看下圖:

  1. 某個應用進程首先調用close主動關閉鏈接,這時TCP發送一個FIN M;
  2. 另外一端接收到FIN M以後,執行被動關閉,對這個FIN進行確認。它的接收也做爲文件結束符傳遞給應用進程,由於FIN的接收意味着應用進程在相應的鏈接上再也接收不到額外數據;
  3. 一段時間以後,接收到文件結束符的應用進程調用close關閉它的socket。這致使它的TCP也發送一個FIN N;
  4. 接收到這個FIN的源發送端TCP對它進行確認。

    這樣每一個方向上都有一個FIN和ACK。

==代碼詳解==

==客戶端:==

#include "stdafx.h"
#include <winsock2.h>
#include <stdio.h>
#include<stdlib.h>
#pragma  comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
    int i;
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA data;
    if(WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }

    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sclient == INVALID_SOCKET)
    {
        printf("invalid socket !");

        return 0;
    }

   struct   sockaddr_in serAddr;                    //地址結構體
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(20155202);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    if (connect(sclient, (struct sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }
int ret=0;
char sendData[255];
char recData[255];
  printf("你好,TCP服務端,我是客戶端!\n");
    while(ret!=-1)
    {
    gets(sendData);
    send(sclient, sendData, strlen(sendData), 0);              //發送

     ret = recv(sclient, recData, 255, 0);                      //接收

                   if(ret > 0)
             {
                  printf("server : ");
                  recData[ret] = 0x00;                 //打印
                   puts(recData);

           }
    }

    WSACleanup();
    return 0;
}

==服務器==

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#include<stdlib.h>
#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[])
{
    //初始化WSA
    WORD sockVersion = MAKEWORD(2,2);
    WSADATA wsaData;
    if(WSAStartup(sockVersion, &wsaData)!=0)
    {
        return 0;
    }
    //建立套接字
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(slisten == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }

    //綁定IP和端口
    struct  sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(20155202);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error !");
    }

    //開始監聽
    if(listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error !");
        return 0;
    }

    //循環接收數據
    SOCKET sClient;
struct    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255];
    while (1)
    {
        printf(" 正在鏈接客戶端...\n");
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
        if(sClient == INVALID_SOCKET)
        {
            printf("accept error !");
            continue;
        }
        printf("接受到一個鏈接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
int ret=0;
char  sendData[255];
      while(ret!=-1)
        {
                                                   //接收數據
         ret = recv(sClient, revData, 255, 0);
        if(ret > 0)
        {
            revData[ret] = NULL;
        printf("Client : ");    puts(revData);
        }
                                                   //發送數據
        gets(sendData);
        send(sClient, sendData, strlen(sendData), 0);
        }
    }
    closesocket(slisten);
    WSACleanup();
    return 0;
}

==運行結果==:

在此基礎上我又進行了寫操做,使得客戶端和服務器之間能夠傳送任意類型的文件,具體實現以下:

==客戶端:==

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

#define BUF_SIZE 1024
int main(){

    char filename[100] = {0};  //文件名

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET sock = socket(AF_INET, SOCK_STREAM,0);
    struct  sockaddr_in sockAddr;


    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(5202);

    printf("你好服務器,我是客戶端\n");

    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

printf("\n服務器像您發送文件請求\n\n您想要接收文件保存到 : \n ");
//輸入文件名,看文件是否能建立成功
    gets(filename);
    FILE *fp = fopen(filename, "wb");  //以二進制方式打開文件
    if(fp == NULL){
        printf(" 建立文件失敗,請按任意鍵繼續 !\n");
        system("pause");
        exit(0);
    }

    //循環接收數據,直到文件傳輸完畢
    char buffer[BUF_SIZE] = {0};  //文件緩衝區
    int zishu;
    printf("\n正在接收寫入數據\n");
    while( (zishu = recv(sock, buffer, BUF_SIZE, 0)) > 0 ){
        fwrite(buffer, zishu, 1, fp);
    }
    puts("\n文件接收完畢\n");
    //文件接收完畢後直接關閉套接字,無需調用shutdown()
    fclose(fp);
    closesocket(sock);
    WSACleanup();
    system("pause");
    return 0;
}

==服務器==

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

#define BUF_SIZE 10240
int main(){

    //初始化WSA
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);

    SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in sockAddr;


    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = INADDR_ANY;
    sockAddr.sin_port = htons(5202);

    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

    listen(servSock, 20);

    SOCKADDR clntAddr;

    int nSize = sizeof(SOCKADDR);

printf(" 服務器20155202  等待客戶端鏈接...\n");

    SOCKET clntSock = accept(servSock, &clntAddr, &nSize);
    printf("\n接收到客戶端來電\n");

  //先檢查文件是否存在
    char filename[BUF_SIZE]; //文件名
    printf("\n輸入你想傳送的文件名\n");
    gets(filename);
    FILE *fp = fopen(filename, "rb");
    if(fp == NULL){
        printf("\n沒那個文件!\n");
        system("pause");
        exit(0);
    }
    printf("\n正在上傳至服務器\n");

    //循環發送數據,直到文件結尾
    char buffer[BUF_SIZE] = {0};  //緩衝區
    int zishu;
    while( (zishu = fread(buffer, 1, BUF_SIZE, fp)) > 0 ){
        send(clntSock, buffer, zishu, 0);
    }
    shutdown(clntSock, SD_SEND);  //文件讀取完畢,斷開輸出流
    printf("\n正在上傳文件至客戶端\n");
    recv(clntSock, buffer, BUF_SIZE, 0);
    printf("\文件發送完畢\n\n");
    fclose(fp);

    closesocket(clntSock);
    closesocket(servSock);
    WSACleanup();
    system("pause");
    return 0;
}

==結果截圖==:

本章問題

  • 問題1:多線程編譯時候線程間工做時間順序?還有pthread_join函數中*retval指針返回值是什麼?

  • 問題1解決方案:我編了一個多線程程序來直觀表現:
  • 由此看出線程之間執行順序隨機,每次變化不同,pthread_join函數中*retval指針返回指向存儲線程返回值的變量,若是成功返回,返回0,不然返回errcode
  • 測試代碼以下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 50
int counter;
void *doit( void * );
int main(int argc, char **argv)
{
    pthread_t tidA, tidB;

    pthread_create( &tidA ,NULL, &doit,(void *)"衛彥忻\n");
    pthread_create( &tidB ,NULL, &doit,(void *)"張旭");

    pthread_join( tidA, NULL );
    pthread_join( tidB, NULL );

    return 0;
}
void * doit( void * vptr)
{
    int i, val;

    for ( i=0; i<NLOOP; i++ ) {
        //val = counter++;
        //printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
        //counter = val + 1;
printf("%s",vptr);
    }

}

代碼託管


結對

  • 結對學習搭檔講解你的總結並獲取反饋
    • 我結對的搭檔:呂宇軒 20155239
      他的問題:

      問題1:SEQ是什麼?

      - 問題1解決方案:

  • SEQ是 Sequential CPU的實現,表如今指令的執行要依次經歷各個階段(Fetch->Decode->Execut->Memory->WriteBack->PC Update).每個指令在一個時鐘週期內完成

問題2:如何將一個字節序列轉變成彙編代碼?

問題2解決方案:個人理解是:彙編代碼最左邊是地址序列,因此必定要有首地址,而後根據表格規則 去尋找指令對應的格式,例如 練習題4.1中 irmovl $15,%ebx,根據指令描述

  • 肯定指令格式,開頭爲30F,而後看寄存器爲%ebx,則對應數字3,因此爲30F3,最後數字15的16進制輸出爲0x0000000f,根據小端原則彙編代碼反過來寫0f000000,因此完整彙編代碼爲30f30f000000,加上句首地址,爲0x100: 30f30f000000

問題3:如何查看Y86-64彙編代碼?

問題3解決方案:

安裝一個Y86模擬器

1、下載Y86模擬器
2、安裝詞法分析工具
  • 在終端輸入
sudo apt-get install bison flex
3、安裝Tcl/Tk 支持圖形界面

繼續輸入:

sudo apt-get install tcl8.5-dev tk8.5-dev tcl8.5 tk8.5
4、解壓sim.tar壓縮包
  • 終端輸入:tar xf sim.tar
  • 修改mkaefile文件(sim文件夾下) 用文檔方式打開
  • GUIMODE=-DHAS_GUI
  • TKLIBS=-L/usr/lib/ -ltk8.5 -ltcl8.5
  • TKINC=-I/usr/include/tcl8.5

5、編譯

  • 進入sim目錄:cd sim
  • 在sim目錄下終端輸入:make clean
  • 在sim目錄下終端輸入:make

    6、運行圖形界面
  • 以pism爲例
  • 進入pipe目錄:cd pipe
  • 運行圖形界面:
./psim -t -g ../y86-code/asum.yo


進入Y86-code 測試一下是否可使用

perfect,能夠了

其餘(感悟、思考等,可選)

學習了網絡編程讓我知道了客戶端和服務器之間的交互的具體步驟,也讓我對各種聊天軟件有了更加深入的瞭解。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第14周 270/200 3/2 33/20

嘗試一下記錄「計劃學習時間」和「實際學習時間」,到期末看看能不能改進本身的計劃能力。這個工做學習中很重要,也頗有用。
耗時估計的公式
:Y=X+X/N ,Y=X-X/N,訓練次數多了,X、Y就接近了。

參考:軟件工程軟件的估計爲何這麼難軟件工程 估計方法

  • 計劃學習時間:33小時
    • 實際學習時間:20小時
  • 改進狀況:

(有空多看看現代軟件工程 課件
軟件工程師能力自我評價表
)

參考資料

相關文章
相關標籤/搜索