SOCKET编程(C语言实现)

来源:互联网 发布:sdn会取代那些传统网络 编辑:程序博客网 时间:2024/06/06 09:45

一,基本步骤
TCP server端实现
1,建立socket套接字
2,绑定套接字(指定ip和端口号)
3,listen(进入监听状态)
4,accept接受客户端请求
5,收发数据
5,关闭套接字

TCP client端实现
1,建立socket套接字
2,connect连接服务器套接字(指定服务器ip和端口号)
3,收发数据
4,关闭套接字

UDP的步骤相似,server端没有listen和accept步骤,client不需要connect步骤。如果client端不显示的指定ip和端口号,server端需要bind自己的ip和端口号,传输层才能将通过该端口的数据送给服务器。


Windows下函数原型:
socket:
SOCKET socket(int af, int type, int protocol)

af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
返回值是socket的ID值,标识socket的唯一性,是一个整形数。

介绍几个重要的结构体

struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};

struct in_addr{
in_addr_t s_addr; //32位的IP地址
};

struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[14]; //IP地址和端口号
};
结构体sockaddr和sockaddr_in等价,sockaddr_in包含详细的ip地址和端口号,所以sockaddr_in更加利于阅读和使用,编程的时候多使用它。

bind:
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen)
将套接字和ip地址和端口绑定。

connect:
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen)
各参数和bind函数一样,区别在于,connect函数是client端用于和server端建立连接。

listen:
int listen(SOCKET sock, int backlog);
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

accept:
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

Windows下数据的接收和发送
send:
从服务器端发送数据使用 send() 函数,它的原型为:
int send(SOCKET sock, const char *buf, int len, int flags);
sock 为要发送数据的套接字,buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项。
返回值和前三个参数不再赘述。
第四个参数flahs一般置为0,或是以下的组合:

MSG_DONTROUTE:不查找路由表
MSG_OOB:接受或发送带外数据
MSG_PEEK:查看数据,并不从系统缓冲区移走数据
MSG_WAITALL :等待任何数据
MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程式里面。
MSG_OOB:表示能够接收和发送带外的数据.
MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是相同的内容。一般在有多个进程读写数据时能够使用这个标志。
MSG_WAITALL:是recv函数的使用标志,表示等到任何的信息到达时才返回。使用这个标志的时候recv会一直阻塞,直到指定的条件满足,或是发生了错误。
1)当读到了指定的字节时,函数正常返回,返回值等于len
2)当读到了文档的结尾时,函数正常返回.返回值小于len
3)当操作发生错误时,返回-1,且配置错误为相应的错误号(errno)

recv:
在客户端接收数据使用 recv() 函数,它的原型为:
int recv(SOCKET sock, char *buf, int len, int flags);
函数参数和send相同。


下面是经典的回声客户端的实现(TCP连接):

server端代码:

#include <stdio.h>#include <winsock2.h>#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll#define BUF_SIZE 100int main(){    WSADATA wsaData;    WSAStartup( MAKEWORD(2, 2), &wsaData);    //创建套接字    SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);    //绑定套接字    sockaddr_in sockAddr;    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充    sockAddr.sin_family = PF_INET;  //使用IPv4地址    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址    sockAddr.sin_port = htons(1234);  //端口    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));    //进入监听状态    listen(servSock, 20);    //接收客户端请求    SOCKADDR clntAddr;    int nSize = sizeof(SOCKADDR);    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);    char buffer[BUF_SIZE];  //缓冲区    int strLen = recv(clntSock, buffer, BUF_SIZE, 0);  //接收客户端发来的数据    send(clntSock, buffer, strLen, 0);  //将数据原样返回    //关闭套接字    closesocket(clntSock);    closesocket(servSock);    //终止 DLL 的使用    WSACleanup();    return 0;}

client端代码:

#include <stdio.h>#include <stdlib.h>#include <WinSock2.h>#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll#define BUF_SIZE 100int main(){    //初始化DLL    WSADATA wsaData;    WSAStartup(MAKEWORD(2, 2), &wsaData);    //创建套接字    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    //向服务器发起请求    sockaddr_in sockAddr;    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充    sockAddr.sin_family = PF_INET;    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");    sockAddr.sin_port = htons(1234);    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));    //获取用户输入的字符串并发送给服务器    char bufSend[BUF_SIZE] = {0};    printf("Input a string: ");    scanf("%s", bufSend);    send(sock, bufSend, strlen(bufSend), 0);    //接收服务器传回的数据    char bufRecv[BUF_SIZE] = {0};    recv(sock, bufRecv, BUF_SIZE, 0);    //输出接收到的数据    printf("Message form server: %s\n", bufRecv);    //关闭套接字    closesocket(sock);    //终止使用 DLL    WSACleanup();    system("pause");    return 0;}

注意:在TCP连接中,client端的socket的ip和端口没有指定,在建立连接的时候,TCP协议会分配一个端口号

原创粉丝点击