NET1_socket-网络编程的基石

1.socket是什么?

1)套接字(socket)是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。通过网络通信的每对进程需要使用一对套接字,即每个进程各有一个。
2)Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
3)将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序。

2.为什么有socket?

客戶服务器(C/S)是我们常用的应用程序框架。大部分的应用层协议,如:FTP,SMTP,HTTP,POP3等要在 客户和服务器之间搭建一个桥梁,用于数据交换。这个时候就需要用到套接字。
我们先来看一张图,宏观上了解一下socket在网络中的位置。
网络分层
网络设备位置socket的网络位置
传输层实现端到端的通信,因此,每一个传输层连接有两个端点。那么,传输层连接的端点是什么呢?不是主机,不是主机的IP地址,不是应用进程,也不是传输层的协议端口。传输层连接的端点就是套接字(socket)
套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。

3.socket的分类

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

domin:套接字中使用的协议簇信息;
type:套接字数据传输类型信息;
protocol:计算机间通信中使用的协议信息,决定套接字中实际采用的最终协议信息;
根据套接字使用的协议划分:
套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
1、基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
2、基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,AF_INET是使用最广泛的一个)
套接字协议族

根据套接字的数据传输方式划分:
流式套接字(SOCK_STREAM):
流式套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。
数据报套接字(SOCK_DGRAM):
数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
原始套接字(SOCK_RAW):
原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为SOCKET_RAW可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过SOCKET_RAW来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包,网络监听技术很大程度上依赖于SOCKET_RAW.
注:原始套接字可以读写内核没有处理的IP数据包,而流式套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

4.套接字的工作流程

先整个流程图:
socket通信流程
1、服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
2、服务器为socket绑定ip地址和端口号
3、服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
4、客户端创建socket
5、客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
6、服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
7、客户端连接成功,向服务器发送连接状态信息
8、服务器accept方法返回,连接成功
9、客户端向socket写入信息
10、服务器读取信息
11、客户端关闭
12、服务器端关闭在这里插入图片描述基于TCP的套接字编程实现流程:

  1. 服务器端流程简介:
    (1)创建套接字(socket)
    (2)将套接字绑定到一个本地地址和端口上(bind)
    (3)将套接字设定为监听模式,准备接受客户端请求(listen)
    (4)阻塞等待客户端请求到来。当请求到来后,接受连接请求,返回一个新的对应于此客户端连接的套接字sockClient(accept)
    (5)用返回的套接字sockClient和客户端进行通信(send/recv);
    (6)返回,等待另一个客户端请求(accept)
    (7)关闭套接字(close)

  2. 客户端流程简介:
    (1) 创建套接字(socket)
    (2) 向服务器发出连接请求(connect)
    (3) 和服务器进行通信(send/recv)
    (4) 关闭套接字(close)
    send和recv函数的理解:
     当调用socket创建套接字时,同时在内核中生成发送和接收缓冲区。

    设置为connect模式时(客户端模式),调用send会将用户自定义的buff中的数据拷贝到发送缓冲区,缓冲区数据的发送由TCP/IP模型完成;

    设置为listen模式时(服务器端模式),发送缓冲区不再使用,接收缓冲区只存放客户端的连接请求。而accpet函数返回的新建套接字sockfd会再生成两个新缓冲区,发送和接收缓冲区。当调用recv时,recv先等待sockfd的发送缓冲区中数据按协议传送完毕,再检查sockfd的接收缓冲区,如果接收缓冲区没有数据或正在传送,则recv等待;否则recv将接收缓冲区中的数据拷贝到用户定义的buff中(ps:当接收缓冲区中数据长度大于buff长度时,recv要调用多次才能完全拷贝完成)。recv返回的是每次实际拷贝的数据长度,若拷贝出错则返回SOCKET_ERROR,若网络中断则返回0。

    send和recv只是从发送/接收缓冲区中拷贝数据,真正的读写数据是由TCP/IP协议完成的。

5.关键结构体

struct sockaddr{
	unsigned short  sa_family;    
	char	        sa_data[14];
};

這是一個通用的套接字地址結構在大部分的套接字函數調用,將被傳遞。這裡是成員字段的描述:
在这里插入图片描述
第二個結構,幫助引用套接字的元素如下:

struct sockaddr_in {
	short int	     sin_family;  
	unsigned short int   sin_port;	
	struct in_addr	     sin_addr;	
	unsigned char	     sin_zero[8];
};

在这里插入图片描述
下一個結構僅用於上述結構中的一個結構域,並擁有32位的netid/主機ID。

struct in_addr {
	unsigned long s_addr;
};

在这里插入图片描述
還有一個更重要的結構。這個結構是用來保持主機相關的信息。

struct hostent
{
  char  *h_name; 
  char  **h_aliases; 
  int   h_addrtype;  
  int   h_length;    
  char  **h_addr_list
#define h_addr  h_addr_list[0]
};

在这里插入图片描述
注: h_addr被定義為h_addr_list[0],以保持向後兼容。
下麵的結構是用來保持服務和相關聯的端口有關的信息。

struct servent
{
  char  *s_name; 
  char  **s_aliases; 
  int   s_port;  
  char  *s_proto;
};

在这里插入图片描述

6.端口和服务

當一個客戶端程序要連接服務器時,客戶端必須有​​識彆要連接的服務器的一種方式。因此客戶端知道可以連接32位網絡地址的主機服務器所在的主機。但是,客戶端如何識彆特定的服務器在該主機上運行的進程呢?
要解決的問題是要確定一個特定的服務器一台主機上運行的進程,TCP和UDP定義一組眾所周知的端口。
對於我們的目的,端口將被定義為1024和65535之間的整數。這是因為所有小於1024的端口號被認為是眾所周知的 - 例如telnet使用端口23,HTTP使用80,FTP使用21,依此類推。
在文件/etc/services中可以找到網絡服務端口分配。如果你正在寫你自己的服務器,那麼必須小心分配一個端口連接到服務器。應該確保該端口應該冇有被其他的服務器分配到(占用)。
它的做法通常指定端口大於5000。但也有許多機構寫自己的服務器端口號大於5000。例如雅虎信使運行端口號為:5050,5060等SIP服務器上運行
端口和服務實例:
這裡是一個小的服務和相關端口列表。可以找到最新的互聯網端口和相關的服務列表 IANA - TCP/IP Port Assignments.
在这里插入图片描述
端口服務功能:

UNIX提供了以下功能從/etc/services文件獲取服務名稱.

struct servent *getservbyname(char *name, char *proto): - 這個調用需要的服務名稱和協議名稱,並返回該服務對應的端口號。

struct servent *getservbyport(int port, char *proto): - 此調用需要的端口號和協議名稱,並返回相應的服務名稱。

每個函數的返回值是一個指針,指向的結構與下麵的形式:

struct servent
{
  char  *s_name; 
  char  **s_aliases; 
  int   s_port;  
  char  *s_proto;
};

在这里插入图片描述

7.核心API

socket 函數:
要執行網絡I/O,進程必須做的第一件事是調用socket函數,指定所需的通信協議類型和協議族等。

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

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

在这里插入图片描述

setsockopt函数
调整套接字属性,使其能够实现地址重用,广播,数据缓存区等功能。

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

(1)、参数sockfd:用于指定要操作的套接字;
(2)、level:要查找的协议所对应的协议层;
a、SOL_SOCKET:通用套接字层
b、IPPROTO_IP:IP协议层,用于设置IP层
c、IPPROTO_TCP:TCP协议层,用于设置TCP。
(3)、optname:查表看要设置为什么功能
(4)、int yes = 1; &yes
(5)、sizeof(int);

关键点:必须要在绑定(bind()函数)之前设置套接字,调用setsockopt();
套接字的查询表在这里插入图片描述

connect 函數:
connect函數使用一個TCP客戶端,TCP服務器建立連接。

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

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

這個調用返回0,則它成功地連接到服務器,否則它給-1的錯誤。
參數:
sockfd: socket函數返回一個套接字描述符.
serv_addr 是一個指向struct sockaddr的包含目的IP地址和端口.
addrlen 設置sizeof為(struct sockaddr).

bind 函數:
分配一個本地協議地址綁定功能的套接字。與互聯網協議的協議地址是一個32位的IPv4地址或128比特的IPv6地址的組合,以及與一個16-bit的TCP或UDP端口號。僅由TCP服務器調用此函數。

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

int bind(int sockfd, struct sockaddr *my_addr,int addrlen);

這個調用返回0,則表示它成功綁定的地址,否則它給-1的錯誤。
參數:

sockfd: 是socket函數返回一個套接字描述符。
my_addr 是一個指向struct sockaddr的包含本地IP地址和端口。
addrlen 設置sizeof為(struct sockaddr).

可以把IP地址和端口自動設置:
端口號0值意味著係統將隨機選擇一個端口和IP地址INADDR_ANY值是指服務器的IP地址將被自動分配。

server.sin_port = 0;  		     
server.sin_addr.s_addr = INADDR_ANY;

注: 不倫不類的端口和服務的基礎教程,所有端口小於1024被保留。所以,可以設置1024以上的端口(但小於65535),同時設置端口不能正在被其他程序使用。

listen 函數:
監聽listen函數被調用時,隻能由一個TCP服務器,它執行兩個動作:
監聽函數將陷入被動套接字未連接的套接字,表明內核應該接受傳入的連接請求定向到該套接字。
這個函數的第二個參數指定連接的內核應此套接字隊列的最大數目。

#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);

這個調用成功返回0,否則它返回-1的錯誤。
參數:

sockfd: socket函數返回一個套接字描述符。

backlog 允許的連接數。

accept 函數:

由TCP服務器調用accept函數返回下一個已完成連接,從完整的連接隊列的前麵。以下是調用的簽名:

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

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

這個調用返回非負描述符成功,否則 -1 為出錯。返回的描述符被假定為一個客戶端的套接字描述符,描述的所有讀寫操作的工作在客戶端通信。
參數:

sockfd: socket函數返回一個套接字描述符。

cliaddr 是一個指向struct sockaddr,包含客戶端的IP地址和端口。

addrlen 它設置於sizeof(struct sockaddr).

send 函數:

發送功能是用來發送數據流套接字或連接的數據報套接字。如果想在未連接的數據報套接字發送數據,必須使用sendto()函數。

可以使用write()係統調用發送數據。此調用解釋在輔助功能的基礎教程。

int send(int sockfd, const void *msg, int len, int flags);

這個調用返回發送出去的字節數,否則將返回-1錯誤.
參數:

sockfd: 是socket函數返回一個套接字描述符。

msg 要發送的數據是一個指針。

len 是要發送的數據(以字節為單位)長度。

flags 設置為 0.

recv 函數:

recv函數是用來接收數據流套接字或連接數據報套接字。如果想在未連接的數據報套接字接收數據,必須使用recvfrom()函數。.

可以使用read()係統調用來讀取數據。此調用解釋在輔助功能的基礎教程。

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

這個調用返回讀入緩衝區的字節數,否則將返回-1錯誤。
參數:

sockfd: socket函數返回一個套接字描述符。

buf 緩衝區讀取信息。

len 最大的緩衝區的長度。

flags 設置為 0.

sendto 函數:

sendto函數用於未連接的數據報套接字發送數據。簡單地說,當使用SCOKET類型為SOCK_DGRAM

int sendto(int sockfd, const void *msg, int len, unsigned int flags,
		   const struct sockaddr *to, int tolen);

這個調用返回發送的字節數否則將返回-1錯誤。
參數:

sockfd: socket函數返回一個套接字描述符。

msg 要發送的數據是一個指針。

len 是要發送的數據(以字節為單位)的長度。

flags 設置為 0.

to 是一個指向結構sockaddr的主機要發送數據。

tolen is set it to sizeof(struct sockaddr).

recvfrom 函數:

recvfrom函數用於未連接的數據報套接字接收數據。簡單地說,當使用SCOKET類型為SOCK_DGRAM時適用。

int recvfrom(int sockfd, void *buf, int len, unsigned int flags
		     struct sockaddr *from, int *fromlen);

這個調用返回讀入緩衝區的字節數,否則將返回-1錯誤。
參數:

sockfd: socket函數返回一個套接字描述符。

buf 緩衝區讀取信息。

len 最大的緩衝區的長度。

flags 被設置為0。

from 是一個指向結構sockaddr的數據的主機被讀取。

fromlen 設置為sizeof(struct sockaddr)

close 函數:

close函數是用來關閉客戶端和服務器之間的通信。

int close( int sockfd );

這個調用成功返回0,否則返回-1錯誤。
參數:

sockfd: socket函數返回一個套接字描述符。

shutdown 函數:

shutdown函數用於正常關閉客戶端和服務器之間的通信。此函數提供了更多的控製在比較close函數。

int shutdown(int sockfd, int how);

這個調用成功返回0,否則返回-1錯誤。
參數:

sockfd: socket函數返回一個套接字描述符。

how: 放入一個數字:

    0 表示接收不允許的,

    1 表明發送不允許

    2 表明禁止發送和接收。如果設置為2,它與close()同樣。

select 函數:

select函數顯示指定文件的描述符是以待準備就緒讀取,準備寫入或有一個錯誤條件。

當應用程序調用recv或recvfrom被阻塞,直到數據到達該套接字。一個應用程序可以做其他有用的處理,而輸入的數據流是空的。另一種情況是,當應用程序從多個套接字接收數據。

調用recv或recvfrom防止立即接收數據與其他Socket上,它的輸入隊列中冇有數據。 select函數調用來解決這個問題,允許程序輪詢所有的套接字手柄,看看他們是否有無阻塞讀取和寫入操作。

 int select(int  nfds,  fd_set  *readfds,  fd_set  *writefds,
     fd_set *errorfds, struct timeval *timeout);

這個調用成功返回0,否則返回-1錯誤。
參數:

nfds: specifies the range of file descriptors to be tested. The select() function tests file descriptors in the range of 0 to nfds-1

readfds:points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to read, and on output indicates which file descriptors are ready to read. Can be NULL to indicate an empty set.

writefds:points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to write, and on output indicates which file descriptors are ready to write Can be NULL to indicate an empty set.

exceptfds :points to an object of type fd_set that on input specifies the file descriptors to be checked for error conditions pending, and on output indicates which file descriptors have error conditions pending. Can be NULL to indicate an empty set.

timeout :poins to a timeval struct that specifies how long the select call should poll the descriptors for an available I/O operation. If the timeout value is 0, then select will return immediately. If the timeout argument is NULL, then select will block until at least one file/socket handle is ready for an available I/O operation. Otherwise select will return after the amount of time in the timeout has elapsed OR when at least one file/socket descriptor is ready for an I/O operation.

返回值選擇多少文件描述符集指定的句柄,選擇返回0,準備就緒I/O如果超時字段指定的時限到達時。下麵的宏存在操縱一個文件描述符集:

FD_CLR(fd, &fdset): 清除位文件描述符fd文件描述符集fdset。

FD_ISSET(fd, &fdset): 返回一個非零值,如果該位被設置為文件描述符fd文件描述符集fdset指向,否則返回0。

FD_SET(fd, &fdset): 位設置文件描述符fd文件描述符集fdset。

FD_ZERO(&fdset): 初始化文件描述符集fdset所有文件描述符的零位。

這些宏的行為是不確定的,如果參數fd小於0或大於或等於FD_SETSIZE。

8.网络例程

简单的TCP网络程序:
在这里插入图片描述

服务器代码:

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

#define SERVER_PORT  5050               //端口号
#define SERVER_IP    "192.168.3.254"    //服务器ip
#define QUEUE_SIZE   5                  //所监听端口队列大小

int main(int argc, char *argv[])
{
    //创建一个套接字,并检测是否创建成功
    int sockSer;                        
    sockSer = socket(AF_INET, SOCK_STREAM, 0);
    if(sockSer == -1){
        perror("socket");
    }

    //设置端口可以重用,可以多个客户端连接同一个端口,并检测是否设置成功
    int yes = 1;
    if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
        perror("setsockopt");
    }

    struct sockaddr_in addrSer,addrCli;        //创建一个记录地址信息的结构体
    addrSer.sin_family = AF_INET;              //所使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置其中的服务器ip

    //将套接字地址与所创建的套接字号联系起来。并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    listen(sockSer, QUEUE_SIZE);       //监听端口队列是否有连接请求,如果有就将该端口设置为可连接状态,等待服务器接收连接

    printf("Server Wait Client Accept......\n");
    //如果监听到有连接请求接受连接请求。并检测是否连接成功,成功返回0,否则返回-1
    int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
    if(sockConn == -1)
        perror("accept");
    else
    {
        printf("Server Accept Client OK.\n");
        printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
        printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
    }

    char sendbuf[256];         //申请一个发送缓存区
    char recvbuf[256];         //申请一个接收缓存区
    while(1)
    {
        printf("Ser:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)    //如果所要发送的数据为"quit",则直接退出。
            break;
        send(sockConn, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
        recv(sockConn, recvbuf, 256, 0);    //接收客户端发送的数据
        printf("Cli:> %s\n",recvbuf);
    }

    close(sockSer);         //关闭套接字
    return 0;
}

客户端代码:

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

#define SERVER_PORT  5050
#define SERVER_IP    "192.168.3.254"

int main(int argc, char *argv[])
{
    //创建客户端套接字号,并检测是否创建成功
    int sockCli;
    sockCli = socket(AF_INET, SOCK_STREAM, 0);
    if(sockCli == -1)
        perror("socket");

    //创建一个地址信息结构体,并对其内容进行设置
    struct sockaddr_in addrSer;     
    addrSer.sin_family = AF_INET;         //使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);  //设置端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置服务器ip

    bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr));    //将套接字地址与所创建的套接字号联系起来

    //创建一个与服务器的连接,并检测连接是否成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("connect");
    else
        printf("Client Connect Server OK.\n");

    char sendbuf[256];     //申请一个发送数据缓存区
    char recvbuf[256];     //申请一个接收数据缓存区
    while(1)
    {
        recv(sockCli, recvbuf, 256, 0);    //接收来自服务器的数据
        printf("Ser:> %s\n",recvbuf);
        printf("Cli:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit", 4) == 0)    //如果客户端发送的数据为"quit",则退出。
            break;
        send(sockCli, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
    }
    close(sockCli);       //关闭套接字
    return 0;
}

简单的UDP网络程序:
在这里插入图片描述
相对与TCP来说,UDP安全性差,面向无链接。所以UDP地实现少了连接与接收连接的操作。所以在收发数据时就不能再用send()和recvfrom()了,而是用sendto()和recvto()指明从哪收发数据。

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

参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)
参数2(buf):指向存有发送数据的缓冲区的指针
参数3(len):缓冲区长度。
**参数4(flags):**flags的值或为0,或为其他

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


参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)
参数2(buf):指向存有接收数据的缓冲区的指针
参数3(len):缓冲区长度
**参数4(flags):**flags的值或为0,或为其他

服务器端代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer;  //创建一个记录地址信息的结构体 
    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip

    //将套接字地址与所创建的套接字号联系起来,并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区
    struct sockaddr_in addrCli;
    while(1)
    {
        recvfrom(sockSer,recvbuf,256,0,(struct  sockaddr*)&addrCli, &addrlen);     //从指定地址接收客户端数据
        printf("Cli:>%s\n",recvbuf);

        printf("Ser:>");    
        scanf("%s",sendbuf);
        sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);    //向客户端发送数据
    }
    return 0;
}

客户端代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockCli == -1){
        perror("socket");
    }

    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip
    socklen_t addrlen = sizeof(struct sockaddr);


    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区

    while(1){
        //向客户端发送数据
        printf("Cli:>");
        scanf("%s",sendbuf);
        sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);   
        接收来自客户端的数据
        recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
        printf("Ser:>%s\n", recvbuf);

    }

    return 0;
}

9.推荐链接
http://tw.gitbook.net/html/socket/index.html   socket基础介绍
https://www.cnblogs.com/yizhizaiYI/articles/5236221.html   setsockopt函数详解

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值