1. 程式人生 > >socket實現的一個基本點對點聊天程式

socket實現的一個基本點對點聊天程式

多個TCP連線或多個應用程式程序可能需要通過同一個 TCP協議埠傳輸資料。為了區別不同的應用程式程序和連線,許多計算機作業系統為應用程式與TCP/IP協議互動提供了稱為套接字(Socket)的介面。

伺服器監聽是指服務端套接字並不定位具體的客戶端套接字,而是處於等待連線的狀態,實時監控網路狀態。 客戶端請求是由客戶端的套接字提出連線請求,要連線的目標是伺服器端套接字。為此,客戶端的套接字必須首先描述它要連線的伺服器的套接字,指出伺服器套接字的地址和埠號,然後再向伺服器端套接字提出連線請求。 連線確認是當伺服器端套接字監聽到或者說接收到客戶端套接字的連線請求時,它就響應客戶端套接字的請求,建立一個新的執行緒,把伺服器端套接字的資訊傳送給客戶端,一旦客戶端確認了此連線,連線即可建立。而伺服器端繼續處於
監聽狀態
,繼續接收其他客戶端的連線請求。 套接字(socket)是套介面描述字的簡稱。和檔案控制代碼相似,SOCKET提供了一咱通訊機制,是WINDOWS的一種通

訊方式。應用程式建立了一個套接字後,就能夠獲得這種機制提供的網路服務功能。對於伺服器來說,它提供

了監聽網路的連線請求;對於客戶機來說,它可以連線到一個給定的主計算機和特定的埠上。客戶端和服務

器端可以通過套接字物件來發送和接收資料。套接字提供了分別基於連線的協議(TCP)等和無連線的協議

(UDP)等,以滿足網路連線的可靠性、穩定性以及高速性的要求。

WINSOCK是網路程式設計介面,它構成了WINDOWS平臺下網路程式設計的基礎。

開放系統互連七層模型(OSI)
應用層——表示層——會話層——傳輸層——網路層——資料鏈路層——物理層

應用層:使用者的應用程式與網路之間的介面
表示層:協商資料交換格式
會話層:允許使用者使用簡單易記的名稱建立連線
傳輸層:提供終端到終端的可靠連線
網路層:使資料路由經過大型網際網路絡
資料鏈路層:決定訪問網路介質的方式
物理層:將資料轉換為可通過物理介質傳送的位

TCP、UDP協議是位傳輸層的協議,而IP協議則是位於網路層的協議。

TCP是傳輸控制協議,它是一種面向連線的協議,向用戶提供可靠的全雙工的位元組流。
TCP關心資料傳輸的準確性。
應用程式利用TCP進行通訊時,傳送方和接收方之間會建立一個虛擬連線,通過這一連線,雙方可以把資料當作

一個雙向的位元組流來進行交流。它就像打電話。我們從摘機撥號開始,到撥通後建立連線、進行通話,再到掛

機斷開連線這一過程,正是抽象的面向連線的具體表現。首先,在開始通話前,撥號,雙方響應,從而建立一

條虛擬的“鏈路”。只有在雙方都處於活動狀態,這條“鏈路”才會保持存在。其次,我們可以通過這條“鏈

路”進行雙向的會話,並且在一般情況下,我們可以通過對方的回答來確定對方是否已經正確聽到了我們所說

的話,這相當於面向連線協議為保證傳輸正確而進行的額外校驗。

UDP是使用者資料報協議,這是一種無連線的協議。UDP是一種不可靠的資料報協議,它不能保證每一個UDP資料報

可以到達目的地。但是,正是由於它的不可靠性,減少了資料確認的過程,所以UDP傳輸資料的效率比較高。
就像是郵信。我們只需封好信封,然後將其投到郵筒中即可,但是我們不能保證郵局在把信件傳送出去和信件

在傳送過程中沒有受到傷害。
總體看來,面向連線的服務可以保證雙方傳遞資料的正確性,但卻要為此進行額外的校驗,通訊雙方建立通訊

通道也需要許多系統開銷。而無連線的服務最大的優點就是速度快,因為它不需要去驗證資料的完整性,也不

會資料是否已接收而操心。


IP是網際協議,自20世紀80年代以來它一直都是網際協議的主力協議,它使用32位地址,為TCP、UDP、ICMP等

協議提供傳送的分組服務。

在WINDOWS網路程式設計中,套接字介面主要有三種類型:流式套接字、資料報套接字以及原始套接字。

流式套接字定義了一種可靠的面向連線的服務,實現了無差錯無重複的順序資料傳輸。對於建立在這種流式套

接字型別上的套接字來說,資料可以是雙向傳輸的位元組流,無長度限制。

資料報套字介面定義了一種無連線的服務,資料通過相互獨立的報文進行傳輸,是無序的,並且不保證可靠。

原始套接字允許對低層協議如IP或ICMP直接訪問,主要用於網路協議的測試,例如WINDOWS帶的PING程式,就是

通過ICMP協議來實現的。
客戶/伺服器模式
在現在的網路應用中,通訊雙方最常見的互動模式便是客戶/伺服器模式。在這種模式下,客戶向伺服器發出服

務請求,伺服器收到請求後為客戶提供相應的服務。


客戶/伺服器模式通常採用監聽/連線的方式實現。伺服器端應用程式在一個埠監聽對服務的請求,也就是說

,服務程序一直處於休眠狀態,直到一個客戶對這個服務提出了連線請求,此時服務執行緒被“喚醒”並且為客

戶提供服務,即對客戶的請求作出適當的反應。

面向連線的協議套接字的呼叫
面向連線的伺服器端首先呼叫SOCKET()建立一個套接字S,用來監聽客戶端的連線請求。接著,呼叫bind()將此

套接字與本機地址、埠繫結起來。然後,呼叫listen()告訴套字S,對進來的連線進行監聽並確認連線請求,

於是S被置於被動的監聽模式。一個正在進行監聽的套接字將給每個請求傳送一個確認資訊,告訴傳送者主機已

經收到連線請求。當時監聽套接字S實際上並不接受連線請求,在客戶端請求被接受後,呼叫
accept()將返回一個與S具有相同屬性,但不能被用來進行監聽,只用來進行資料收發的資料套接字NS,作為與

客戶端套接字相對應的連線的另一個端點。對於該客戶端套接字後續的所有操作,都應該通過NS來完成。監聽

套接字S仍然用於接收其他客戶的連線請求。

面向連線的伺服器一般是迸發伺服器。在WINDOWS平臺上,我們往往在呼叫accept()返回NS後,會建立一個請求

/應答執行執行緒,將NS作為引數之一傳遞給該執行緒,由該執行緒來完成客戶端與伺服器端複雜的請求應答工作,而

主執行緒會再次呼叫accept(),以接收新的客戶端連線請求。

面向連線的客戶端也會呼叫socket()建立一個套接字C,但使用像TCP這樣的面向連線的協議時,客戶端不必關

心協議使用什麼樣的本機地址,所以不用呼叫bind()。客戶端呼叫connect()向伺服器端發出連線請求,在與服

務器建立連線之後,客戶端和伺服器端就存在了一條虛擬的“管道”,客戶端套字C和伺服器端套接字NS構成了

“管道”的兩個端點。客戶端和伺服器端通過這個“管道”進行資料交換,多次呼叫send()/recv()來進行請求

/應答,最終完成服務後關閉用於傳輸的套接字C和NS,並斷開連線,結束此次會話。

面向無連線協議的套接字的呼叫
採用無連線協議(UDP)時,伺服器一般都是面向事務的。一個請求和一個應答就完成了客戶程式與伺服器程式

之間的相互作用。

無邊的伺服器使用socket()和bind()來建立和繫結套接字S。與面向連線的伺服器端不同,我們不必呼叫

listen()和accept(),只需要呼叫recvFrom()在套接字S上等待接收資料就可以了。因為是無連線的,因此網路

上任何一臺機器傳送的套接字S的資料都可以收到。從這一點上你可以想象,它們是無序的。

無連線的伺服器一般都是迭代伺服器。它們接收到一個數據報後,馬上進行相應處理,直到處理完成後,才開

始下一個資料報的接收、處理。所以採用無連線協議時,客戶端和伺服器端的互動往往是很簡單的,一問一答

或只問不答的方式很常見。

無連線的伺服器端只有在停止服務時,才會關閉套接字。

無連線的客戶端則更簡單,只需要呼叫socket()建立一個套接字C,就可以利用sendto()和recvfrom()與伺服器

的資料進行交換。在完成會話後呼叫closeSocket()關閉套接字C。

伺服器與客戶端通過已連線套接字進行接收與傳送訊息!

p2pcli.c

#include <unistd.h>

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

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
    exit(EXIT_SUCCESS);
}

int main(void)
{
    int sock;
    if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        ERR_EXIT("socket");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(5188);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        ERR_EXIT("connect");

    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork");

    if (pid == 0)
    {
        char recvbuf[1024];
        while (1)
        {
            memset(recvbuf, 0, sizeof(recvbuf));
            int ret = read(sock, recvbuf, sizeof(recvbuf));
            if (ret == -1)
                ERR_EXIT("read");
            else if (ret == 0)
            {
                printf("peer close\n");
                break;
            }
            
            fputs(recvbuf, stdout);
        }
        close(sock);
        kill(getppid(), SIGUSR1);
    }
    else
    {
        signal(SIGUSR1, handler);
        char sendbuf[1024] = {0};
        while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {
            write(sock, sendbuf, strlen(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
        close(sock);
    }


    
    return 0;

}

p2psrv.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

void handler(int sig)
{
    printf("recv a sig=%d\n", sig);
    exit(EXIT_SUCCESS);
}

int main(void)
{
    int listenfd;
    if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
/*    if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
        ERR_EXIT("socket");

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(5188);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
    /*inet_aton("127.0.0.1", &servaddr.sin_addr);*/

    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        ERR_EXIT("setsockopt");

    if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        ERR_EXIT("bind");
    if (listen(listenfd, SOMAXCONN) < 0)
        ERR_EXIT("listen");

    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof(peeraddr);
    int conn;
    if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
        ERR_EXIT("accept");

    printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));


    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork");
    
    if (pid == 0)
    {
        signal(SIGUSR1, handler);
        char sendbuf[1024] = {0};
        while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
        {
            write(conn, sendbuf, strlen(sendbuf));
            memset(sendbuf, 0, sizeof(sendbuf));
        }
        printf("child close\n");
        exit(EXIT_SUCCESS);
    }
    else
    {
        char recvbuf[1024];
        while (1)
        {
            memset(recvbuf, 0, sizeof(recvbuf));
            int ret = read(conn, recvbuf, sizeof(recvbuf));
            if (ret == -1)
                ERR_EXIT("read");
            else if (ret == 0)
            {
                printf("peer close\n");
                break;
            }
            
            fputs(recvbuf, stdout);
        }
        printf("parent close\n");
        kill(pid, SIGUSR1);
        exit(EXIT_SUCCESS);
    }
    
    return 0;
}

makefile:

.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=echosrv echocli echosrv2 p2psrv p2pcli
all:$(BIN)
%.o:%.c
    $(CC) $(CFLAGS) -c $< -o [email protected]
clean:
    rm -f *.o $(BIN)



相關推薦

socket實現一個基本點聊天程式

多個TCP連線或多個應用程式程序可能需要通過同一個 TCP協議埠傳輸資料。為了區別不同的應用程式程序和連線,許多計算機作業系統為應用程式與TCP/IP協議互動提供了稱為套接字(Socket)的介面。 伺服器監聽是指服務端套接字並不定位具體的客戶端套接字,而是處於等待連線的

聊天程式實現

服務端 #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/

netty 聊天程式

package com.anxpp.im.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.C

HTML5 WebSocket實現聊天的示例代碼

HTML案例分析 HTML5講解 HTML5的websocket與Tomcat實現了多人聊天,那是最簡單也是最基本的,其中註意的就是開發環境,要滿足jdk1.7和tomcat8,當然了tom7的7.063也行,在網站上找到了用google關於websocket的點對點聊天,更好的是可以和大多數系統很好

實現一個能夠錶達式進行求值的控制檯程式

我這個專案的碼雲地址是https://gitee.com/wangzhiming/personal_project41678.git 下邊這張表格記錄了我預期做這個專案的時間和實際上消耗的時間,看得出來我寫的速度還是趕不上我希望的速度,我的繼續努力!!! PSP2.1

Spring Boot整合websocket實現群聊,聊天,圖片傳送,音訊傳送

參考:基於https://blog.csdn.net/qq_38455201/article/details/80374712  基礎上進行新增圖片傳送和音訊傳送功能   單點圖片傳送: 單點音訊傳送: 音訊傳送相關js參考:https://github.

java使用Netty實現聊天

最近學習伺服器開放,實現客戶端(APP)與硬體裝置之間的通訊,我一想到socket,經過查詢資料發現socket實現起來非常麻煩,同時也發現一個比較好的框架netty,覺得很不錯,就開始嘗試用他來擼一個點對點聊天系統,想了解的小夥伴可以自行去學習一下netty 一、一開始是導包,我是匯入

Socket通訊 一個伺服器多個客戶端,不能延遲接受資訊的實現

實驗要求: 1.一個時間點,無論哪個基站有資料傳送,伺服器必須同時接收。不能延遲等待這個客戶端接收完畢再接收另一個客戶端; 2.客戶端不能關閉,一直連線,以防資料遺漏 知識點: 多執行緒 同時開啟多個任務,不用按照程式的執行順序來; 基本的程式碼如下: pack

java WebSocket實現簡單的聊天室(包括群發和聊天

今天突然看到了WebSocket然後就網上找了一個例子,然後修改了下,實現了簡單的聊天室,包括群聊和點對點聊天。 使用的程式碼如下 jsp程式碼: <%@ page language="java" import="java.util.*" pageEncoding="

C#使用Socket實現一個socket服務器與多個socket客戶端通信

當前 rec inf hide 負責 new 數據庫 class 多臺   在分布式調度系統中,如果要實現調度服務器與多臺計算節點服務器之間通信,采用socket來實現是一種實現方式,當然我們也可以通過數據存儲任務,子節點來完成任務,但是往往使用數據作為任務存儲都需要定制開

Socket實現一個簡單的半雙工通信

沒有 sta exception 分析 對講 想去 info 信息 keyword Socket是client進行在網絡與server進行數據交互的一種基本通信方式。

Python-通過socket實現一個小型的端口檢測工具

socket 結果 soc png cep light true python bsp 實驗機器IP:192.168.220.139,端口開放情況 代碼 # -*- coding:utf-8 -*- __author__ = "MuT6 Sch01aR" import

不到50行程式碼實現一個請求併發數做限制的通用RequestDecorator

使用場景 在開發中,我們可能會遇到一些對非同步請求數做併發量限制的場景,比如說微信小程式的request併發最多為5個,又或者我們需要做一些批量處理的工作,可是我們又不想同時對伺服器發出太多請求(可能會對伺服器造成比較大的壓力)。這個時候我們就可以對請求併發數進行限制,並且使用排隊機制讓請求有序的傳送出去。

php從零搭建即時通訊(一.聊天)

目錄 零.在thinkphp5環境下搭建gatewayWork環境 2.下載完成後解壓到你TP5專案的vendor目錄下即可,如圖 3.點選start_for_win.bat檔案即可成功啟動你的webSocked伺服器,如果你是lin

基於ftp實現一個類dropbox檔案同步程式

最近要在實驗室和宿舍兩頭跑,同一臺電腦上還有win和linux等多個系統,想要在任何一個地點和平臺上繼續看之前的文獻實在有點麻煩,只能拿U盤來拷,但是我的U盤又很容易掉,萬一掉了那辛苦工作的成果可就全沒了。你說網盤吧,百度網盤又不合我意,同步太麻煩了。最符合我想

用Python socket實現一個簡單的http伺服器(post 與get 的區別)、CGIHTTPServer 簡單應用

#!/usr/bin/env python #coding=utf-8import socketimport re HOST = '' PORT = 8000#Read index.html, put into HTTP response dataindex_content = '''HTTP/1.x 200

綜合例項:用C語言實現一個自定義的shell程式

      一個shell需要實現若干功能,比如解釋執行命令,支援輸入輸出重定向,支援管道,後臺執行程式等。首先對要實現的功能做一個簡要介紹:       (1)輸出重定向:就是把執行某命令後的結果輸出到某個檔案。例如: ls -al > list.txt  

JAVA實驗二:編碼實現一個輸入陣列的數從小到大排序同時使用二分法查詢某一個數(遞迴和非遞迴)

編碼實現一個類 (1)提供一個靜態方法,可以將輸入的一個int[]陣列按照從小到大的順序排列; (2)提供靜態方法,對排好序的陣列使用折半(二分)查詢(使用遞迴和非遞迴兩種形式分別實現)查詢某一個整數。 答案 import java.util.*; public class

4.13 Go語言專案實戰:聊天

需求摘要 實現一個分散式點對點的聊天系統,所有節點都是對等的,不需要中央伺服器 實現註冊節點名稱,節點之間通過節點名稱發起會話 思路分析 節點同時具備服務端和客戶端的職能 服務端只負責接收其它節點主動傳送過來的訊息 客戶端只負責主動向其

實現一個隨機點名的小程式 ,並且將方法功能獨立出來,減少冗餘

import java.util.Scanner; import java.util.Random; /* 實現隨機點名器 1. 儲存所有學生姓名 2. 預覽所有學生姓名,遍歷陣列 3. 隨機數作為索引,到陣列中找元素 將功能獨立出來, 作成方法,呼叫方法即可